dbus-deviation-0.6.0/0000775000175000017500000000000013035747236015242 5ustar philipphilip00000000000000dbus-deviation-0.6.0/.gitignore0000664000175000017500000000012413026466535017227 0ustar philipphilip00000000000000/*.egg-info/ /*.egg /.eggs/ /build/ /dist/ /htmlcov/ /.coverage /.be/id-cache *.pyc dbus-deviation-0.6.0/pre-push.hook0000664000175000017500000000223513026466535017671 0ustar philipphilip00000000000000#!/bin/sh # A hook script to update the D-Bus API signature database every time a release # tag is pushed. See the git-hooks documentation for the parameters it’s called # with. See $(top_srcdir)/dbus-deviation.mk for more information about how this # project uses dbus-deviation to check D-Bus API stability. # # If the API signature database is updated, a git note will be added to the new # tag for each D-Bus API in the project, and the notes will be pushed remotely. remote="$1" url="$2" # Get the configuration from the Makefile. # This is pretty terrible. eval `make dbus-deviation-mk-config` if [ "$GIT" == "" ]; then GIT=git fi if [ "$GIT_DIR" == "" ]; then GIT_DIR=`git rev-parse --git-dir` fi if [ "$GIT_WORK_TREE" == "" ]; then GIT_WORK_TREE=`pwd` fi IFS=' ' while read local_ref local_sha remote_ref remote_sha; do if ! $GIT rev-parse --verify $local_ref^{tag} &> /dev/null; then continue fi # Ignore failures from this. dbus-interface-vcs-helper \ --git "$GIT" \ --git-dir "$GIT_DIR" \ --git-work-tree "$GIT_WORK_TREE" \ --git-refs "$dbus_api_git_refs" \ --git-remote "$remote" \ dist --ignore-existing $dbus_api_xml_files done exit 0 dbus-deviation-0.6.0/setup.cfg0000664000175000017500000000016413035747236017064 0ustar philipphilip00000000000000[pep8] exclude = *.egg,docs/conf.py,version.py,setup.py [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 dbus-deviation-0.6.0/dbus-deviation.doap0000664000175000017500000000214313026466535021024 0ustar philipphilip00000000000000 dbus-deviation dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation. Philip Withnall dbus-deviation-0.6.0/website/0000775000175000017500000000000013035747236016704 5ustar philipphilip00000000000000dbus-deviation-0.6.0/website/errors.html0000664000175000017500000004614313026466535021116 0ustar philipphilip00000000000000 dbus-deviation: Error codes

dbus-deviation: Error codes

A list of all the possible error codes produced by dbus-deviation, along with potential causes, what problems result, and how to avoid them.

Parser errors

Error code Compatibility Description
duplicate-interface error Two D-Bus interfaces with the same name were defined in a single API file. One of the interfaces may be misnamed.
duplicate-method error Two methods with the same name were defined in a single D-Bus interface. One of the methods may be misnamed.
duplicate-property error Two properties with the same name were defined in a single D-Bus interface. One of the properties may be misnamed.
duplicate-signal error Two signals with the same name were defined in a single D-Bus interface. One of the signals may be misnamed.
missing-attribute error An XML node in the API file was missing a required attribute. Typically, a ‘name’ attribute is required — check the Introspection Data Format specification.
unknown-node error An XML node in the API file was not recognized. Check the Introspection Data Format specification.

Comparator errors

Error code Compatibility Description
argument-added backwards-incompatible A new argument was added to a method or signal. This is backwards incompatible as all existing call sites and signal handlers need to be changed to handle it.
argument-direction-changed-*-* backwards-incompatible An argument’s direction was changed from ‘in’ to ‘out’ or vice-versa. This is backwards incompatible as all existing call sites and signal handlers need to be changed to write the argument when they would have previously read it, or vice-versa.
argument-name-changed info An argument changed name. This is not an API break, as arguments are looked up by index. Note that it is an API break if two arguments have swapped places though. You should check that the name change is not accompanied by a change in the semantics of the argument.
argument-removed backwards-incompatible An argument was removed from a method or signal. This is backwards incompatible as all existing call sites and signal handlers need to be changed to stop using it.
argument-type-changed backwards-incompatible An argument changed type. This is backwards incompatible as all existing call sites and signal handlers need to be changed to use the new type. This might have been caused by another argument being added or removed; so check other error messages too.
c-symbol-changed info The C symbol corresponding to a D-Bus API changed name. This does not affect stability of the D-Bus API, but may change the API of any generated C bindings. You should check whether that is the case.
deprecated info A D-Bus API has been marked as deprecated using the org.freedesktop.DBus.Deprecated annotation. The API can still be used, but you might want to port applications away from using it, to use the documented replacement instead.
ecs-changed-true-false, ecs-changed-true-const, ecs-changed-invalidates-false, ecs-changed-invalidates-const forwards-incompatible A D-Bus property which previously emitted the org.freedesktop.DBus.Properties.PropertiesChanged signal when its value changed will no longer emit that signal. It either now has a constant value (for the lifetime of the D-Bus object containing the property), or simply is no longer guaranteed to emit the signal when its value changes. Applications which previously handled notifications for this property will no longer receive them, but will otherwise continue to function.
ecs-changed-false-true, ecs-changed-false-invalidates, ecs-changed-const-true, ecs-changed-const-invalidates backwards-incompatible A D-Bus property which previously did not emit the org.freedesktop.DBus.Properties.PropertiesChanged signal will now emit that signal when its value changes. The signal may contain the updated value of the property, or may simply mark the property as ‘invalidated’. Applications which previously assumed the property had a constant value should be changed to handle notifications for it.
ecs-changed-true-invalidates backwards-incompatible A D-Bus property which previously emitted its updated value in the org.freedesktop.DBus.Properties.PropertiesChanged signal when its value changed will no longer include the updated value, and will instead notify the property as ‘invalidated’. Applications which expected the property to be listed in the changed_properties argument of the org.freedesktop.DBus.Properties.PropertiesChanged signal should be changed to look in the invalidated_properties argument — or to look in both, for maximum compatibility.
ecs-changed-invalidates-true backwards-incompatible A D-Bus property which previously did not emit its updated value in the org.freedesktop.DBus.Properties.PropertiesChanged signal when its value changed will now include the updated value, and will no longer notify the property as ‘invalidated’. Applications which expected the property to be listed in the invalidated_properties argument of the org.freedesktop.DBus.Properties.PropertiesChanged signal should be changed to look in the changed_properties argument — or to look in both, for maximum compatibility.
ecs-changed-const-false backwards-incompatible A D-Bus property which was previously marked as constant, with a value which did not change throughout the lifetime of the containing D-Bus object, is now marked as non-constant. Its value may now change; however, such changes will not be notified using the org.freedesktop.DBus.Properties.PropertiesChanged signal. Applications should be changed to re-query the property when appropriate, and to no longer assume that its value is constant.
ecs-changed-false-const forwards-incompatible A D-Bus property which was previously marked as non-constant, is now marked as constant, with a value which cannot change throughout the lifetime of the containing D-Bus object. The property will continue to not be notified through the org.freedesktop.DBus.Properties.PropertiesChanged signal. Applications may be changed to assume the property’s value is constant, and hence no longer need to re-query its value after the initial query.
interface-added forwards-incompatible A new D-Bus interface was added to the API, and can now be used in applications. You must bump your application’s dependency on the D-Bus API version, or check that the new interface is implemented at runtime before using it, otherwise your application will not run correctly against older versions of the D-Bus service.
interface-removed backwards-incompatible A D-Bus interface was removed from the API, and can no longer be used in applications. This is a backwards-incompatible change, and all applications which were using that interface must be ported away from it, or have runtime version checks added so they only use the interface when the D-Bus service implements it at runtime.
method-added forwards-incompatible A new method was added to a D-Bus interface in the API, and can now be used in applications. You must bump your application’s dependency on the D-Bus API version, or check that the new method is available at runtime before using it, otherwise your application will not run correctly against older versions of the D-Bus service.
method-removed backwards-incompatible A method was removed from a D-Bus interface in the API, and can no longer be used in applications. This is a backwards-incompatible change, and all applications which were using that method must be ported away from it, or have runtime version checks added so they only use the method when the D-Bus service implements it at runtime.
property-access-changed-read-readwrite, property-access-changed-write-readwrite forwards-incompatible A property changed access flags from ‘read’ or ‘write’ to ‘readwrite’, making it less restrictive. Applications may now get and set the property, which was not possible before. This is a forwards-incompatible change, so any applications which start using the lessened restrictions should gracefully handle failure to use them, so that they continue to work with older versions of the D-Bus service.
property-access-changed-read-write, property-access-changed-write-read, property-access-changed-readwrite-read, property-access-changed-readwrite-write backwards-incompatible A property changed access flags to be more restrictive. This is a backwards incompatible change, and applications will need to be changed to stop accessing the property how they were before, or to gracefully handle failure of set or get calls on the property.
property-added forwards-incompatible A new property was added to a D-Bus interface in the API, and can now be used in applications. You must bump your application’s dependency on the D-Bus API version, or check that the new property is available at runtime before using it, otherwise your application will not run correctly against older versions of the D-Bus service.
property-removed backwards-incompatible A property was removed from a D-Bus interface in the API, and can no longer be used in applications. This is a backwards-incompatible change, and all applications which were using that property must be ported away from it, or have runtime version checks added so they only use the property when the D-Bus service implements it at runtime.
property-type-changed backwards-incompatible A property changed type. This is backwards incompatible as all existing set and get sites need to be changed to use the new type.
reply-added backwards-incompatible A D-Bus method which was previously marked as not returning a reply will now return a reply. All call sites using the method should be modified to asynchronously wait for a reply and handle any errors or return values it provides. This may be coupled with a change in the method’s type to add new ‘out’ arguments (argument-added), which should be handled as appropriate.
reply-removed backwards-incompatible A D-Bus method which was previously marked as returning a reply will now not return a reply. All call sites using the method should be modified to not wait for a reply, and any code which handles errors or return values can be removed. This may be coupled with a change in the method’s type to remove ‘out’ arguments (argument-removed), support for which should be removed as appropriate.
signal-added forwards-incompatible A new signal was added to a D-Bus interface in the API, and can now be connected to in applications. You must bump your application’s dependency on the D-Bus API version, or check that the new signal is available at runtime before connecting to it, otherwise your application will not run correctly against older versions of the D-Bus service.
signal-removed backwards-incompatible A signal was removed from a D-Bus interface in the API, and can no longer be used in applications. This is a backwards-incompatible change, and all applications which were connecting to that signal must be ported away from it, or have runtime version checks added so they only connect to the signal when the D-Bus service implements it at runtime.
undeprecated info A D-Bus API which was previously marked as deprecated using the org.freedesktop.DBus.Deprecated annotation is no longer marked as deprecated. You may start using it in applications again, although you should check whether the un-deprecation was intentional first.

Bugs and problems

If you have found a bug on this page, have found missing documentation, or if some of the documentation is unclear, please file a bug.

dbus-deviation-0.6.0/website/index.html0000664000175000017500000001111613026466535020701 0ustar philipphilip00000000000000 dbus-deviation: a project for parsing D-Bus introspection XML and processing it in various ways

dbus-deviation: a project for parsing D-Bus introspection XML and processing it in various ways

dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation.

A second Python module, dbusapi, is provided for parsing D-Bus introspection XML to produce an AST representing a D-Bus interface.

dbus-deviation is licenced under the LGPLv2.1+.

Versions 0.4.0 and earlier were licenced under the same licence as the D-Bus project: your choice of the Academic Free License version 2.1, or the GPLv2+. For version 0.5.0, the licence was changed to LGPLv2.1+ as it’s a more standard licence with simpler implications.

Download and installation

Tarballs for released versions of dbus-deviation are below. All have been signed with key 4B2EAFA0 (Philip Withnall).

Building and installing follows the standard Python setuptools process:

tar -xzf ./dbus-deviation-$VERSION.tar.gz
cd ./dbus-deviation-$VERSION
./setup.py build
./setup.py --prefix=$PREFIX install

Documentation

For installation and quick-start information, see the README file.

API documentation is available on pythonhosted, or available here.

Details on all errors produced by dbus-deviation is available here.

Source code and bugs

Git repository

The source code for dbus-deviation is stored in git, which can be viewed online or cloned:

git clone https://github.com/pwithnall/dbus-deviation.git

Bugs

dbus-deviation uses Bugs Everywhere for feature enhancement tracking, so to view all currently open bugs, clone the dbus-deviation git repository and run:

be list

To file a bug, please contact the authors by e-mail.

Contact

dbus-deviation is written by Philip Withnall, as part of R&D work by Collabora.

If you have any bug reports, questions, suggestions or criticism of the project, please e-mail me (philip tecnocode co uk).

dbus-deviation-0.6.0/dbus-deviation.mk0000664000175000017500000002016613026466535020515 0ustar philipphilip00000000000000# Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . # This Makefile can be included in the top-level Makefile.am in a project to # provide rules for checking the API compatibility of D-Bus interface between # different tagged releases of a project. # # To add it to your project, copy this file into your project, add it to git, # and add the following lines to your top-level Makefile.am: # # dbus_api_xml_files = list of D-Bus interface XML files # dbus_api_checkflags = --fatal-warnings # -include $(top_srcdir)/dbus-deviation.mk # # Do not list this file in EXTRA_DIST or similar — it is designed to be used # from git checkouts only. Further configuration options are documented below. # # Then, run the following command to initialise the database of past versions # of the XML files: # # make dbus-deviation-mk-install # # It is safe to run this command multiple times, though there is no need to. # It will not make changes to your remote repository, but will give you a # `git push` command to run to push the database to a remote. # # Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s # executable. This script will automatically update the API signature database # when a new release tag is pushed to the git remote. It is required for dist # to succeed. # # If your project builds D-Bus interfaces at runtime, rather than automatically # generating the code for them from XML files, you must populate the database # manually. For each tagged release, and for each D-Bus interface, run: # # xml_file=path/to/generated-api-description.xml # tag=release_tag_name # dbus_api_git_refs=notes/dbus/api # matches the Makefile config # git_remote_origin=origin # matches the Makefile config # # git checkout "$tag" # # Build your program. # # Run your program. # # Call the org.freedesktop.DBus.Introspectable.Introspect() method. # # Save its output to $xml_file. # # notes=$(git hash-object -w "$xml_file") # xml_basename=$(basename "$xml_file") # git notes --ref "refs/$(dbus_api_git_refs)/$xml_basename" \ # add -C "$notes" "$tag" # git push "$(git_remote_origin)" refs/$(dbus_api_git_refs)/* # # The following configuration variables are mandatory: # # dbus_api_xml_files: # Space-separated list of paths to the API XML files. # # The following are optional: # # git_remote_origin (default: origin): # Remote to push/pull the API signature database to/from. # dbus_api_diff_warnings (default: all): # Comma-separated list of warnings to enable when running # dbus-interface-diff. # dbus_api_checkflags (default: empty): # Flags to pass to the ‘check’ operation of dbus-interface-vcs-helper. # Typically, this should be --fatal-warnings. # dbus_api_distflags (default: empty): # Flags to pass to the ‘dist’ operation of dbus-interface-vcs-helper. # dbus_api_git_refs (default: notes/dbus/api): # Path beneath refs/ where the git notes will be stored containing the # API signatures database. # GIT (default: git): # Path of the git program to run, including any custom arguments to # pass to every invocation of git. # # The Makefile hooks in to dist-hook and check-local to update the API # signature database and to check for differences between the most recent # release and the current working tree. # # This file support out-of-tree builds, and git repositories with non-standard # GIT_WORK_TREE or GIT_DIR settings (bare repositories). # Mandatory configuration. dbus_api_xml_files ?= # Optional configuration. git_remote_origin ?= origin dbus_api_diff_warnings ?= info,forwards-compatibility,backwards-compatibility dbus_api_checkflags ?= dbus_api_distflags ?= dbus_api_git_refs ?= notes/dbus/api dbus_api_git_work_tree ?= $(top_srcdir) dbus_api_git_dir ?= $(dbus_api_git_work_tree)/.git GIT = git # Silent rules for dbus-interface-vcs-helper dbus_interface_vcs_helper_v = $(dbus_interface_vcs_helper_v_$(V)) dbus_interface_vcs_helper_v_ = $(dbus_interface_vcs_helper_v_$(AM_DEFAULT_VERBOSITY)) dbus_interface_vcs_helper_v_0 = --silent dbus_interface_vcs_helper_v_1 = V_api = $(v_api_$(V)) v_api_ = $(v_api_$(AM_DEFAULT_VERBOSITY)) v_api_0 = @echo " API " $@; v_api_1 = # For each XML file in $(dbus_api_xml_files), add it to the API signature # database for the most recent git tag. dist-dbus-api-compatibility: $(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \ --git "$(GIT)" \ --git-dir "$(dbus_api_git_dir)" \ --git-work-tree "$(dbus_api_git_work_tree)" \ --git-refs "$(dbus_api_git_refs)" \ --git-remote "$(git_remote_origin)" \ dist --ignore-existing $(dbus_api_distflags) \ $(dbus_api_xml_files) # Check the pre-push hook is installed, otherwise the API signature database # will not get pushed to the remote after the release tag is created. dist-dbus-api-compatibility-check-hook: @if [ ! -x "$(dbus_api_git_dir)/hooks/pre-push" ] || \ ! grep dbus-interface-vcs-helper "$(dbus_api_git_dir)/hooks/pre-push"; then \ echo "error: dbus-deviation git hook is not installed. Copy pre-push.hook to" 1>&2; \ echo " $(dbus_api_git_dir)/hooks/pre-push" 1>&2; \ echo " to enable updates to the D-Bus API signature database." 1>&2; \ echo " See dbus-deviation.mk for more details." 1>&2; \ echo "Aborting." 1>&2; \ exit 1; \ fi dist-hook: dist-dbus-api-compatibility dist-dbus-api-compatibility-check-hook .PHONY: dist-dbus-api-compatibility dist-dbus-api-compatibility-check-hook # Check that the D-Bus API signatures for the two refs given as OLD_REF and # NEW_REF are compatible; error if they are not. The refs should typically be # tags, specified as ‘tag_name^{tag}’, since the API signature database is not # stored for non-tag refs by default. # # OLD_REF defaults to the most recent git tag. NEW_REF defaults to the current # working tree — so running this rule with neither specified will check the # current working tree against the latest release. If this happens during # distcheck, it effectively checks the release-in-progress against the # previous release, which is exactly what is expected of distcheck. # # If this is run on a source directory where $(dbus_api_git_dir) does not # exist, it will print a message and exit successfully. This supports the use # case where `make distcheck` is run on an extracted tarball of a release. check-dbus-api-compatibility: $(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \ --git "$(GIT)" \ --git-dir "$(dbus_api_git_dir)" \ --git-work-tree "$(dbus_api_git_work_tree)" \ --git-refs "$(dbus_api_git_refs)" \ --git-remote "$(git_remote_origin)" \ check \ --diff-warnings "$(dbus_api_diff_warnings)" \ $(dbus_api_checkflags) \ "$(OLD_REF)" "$(NEW_REF)" check-local: check-dbus-api-compatibility .PHONY: check-dbus-api-compatibility # Installation rule to set up an API signature database for each existing tag. # It is safe to run this multiple times. dbus-deviation-mk-install: $(V_api)dbus-interface-vcs-helper $(dbus_interface_vcs_helper_v) \ --git "$(GIT)" \ --git-dir "$(dbus_api_git_dir)" \ --git-work-tree "$(dbus_api_git_work_tree)" \ --git-refs "$(dbus_api_git_refs)" \ --git-remote "$(git_remote_origin)" \ --no-push \ install $(dbus_api_xml_files) .PHONY: dbus-deviation-mk-install # Helper for the pre-push git hook to export the configuration. # Exports in bash syntax so this can be directly evaled. dbus-deviation-mk-config: @echo "dbus_api_git_refs=\"$(dbus_api_git_refs)\"" @echo "dbus_api_xml_files=\"$(dbus_api_xml_files)\"" .PHONY: dbus-deviation-mk-config dbus-deviation-0.6.0/dbusapi/0000775000175000017500000000000013035747236016671 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusapi/interfaceparser.py0000664000175000017500000001013413026466535022417 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Module providing an `InterfaceParser` object for parsing D-Bus introspection XML files into abstract syntax trees (ASTs). """ # pylint: disable=no-member from lxml import etree from dbusapi.ast import AstLog, Node, TP_DTD def _skip_non_node(elem): for node in elem.getchildren(): if node.tag == 'node': return node return None class ParsingLog(AstLog): """A specialized AstLog subclass for parsing issues""" def __init__(self, filename): """ Construct a new ParsingLog. Args: filename: str, the name of the file being parsed. """ super(ParsingLog, self).__init__() self.__filename = filename self.domain = 'parser' def _create_entry(self, code, message): return self.__filename, self.domain, code, message class InterfaceParser(object): """ Parse a D-Bus introspection XML file. This validates the file, but not exceedingly strictly. It is a pragmatic parser, designed for use by the InterfaceComparator rather than more general code. It ignores certain common extensions found in introspection XML files, such as documentation elements, but will fail on other unrecognised elements. """ def __init__(self, filename): """ Construct a new InterfaceParser. Args: filename: path to the XML introspection file to parse """ self._filename = filename self._log = ParsingLog(filename) @staticmethod def get_output_codes(): """Return a list of all possible output codes.""" return ParsingLog(None).issue_codes def get_output(self): """Return a list of all logged parser messages.""" return self._log.issues def _get_root(self): root = etree.parse(self._filename).getroot() # Handle specifications wrapped in tp:spec. if root.tag == '{%s}spec' % TP_DTD: root = _skip_non_node(root) if root is not None and root.tag != 'node': self._log.log_issue('unknown-node', 'Unknown root node ‘%s’.' % root.tag) root = _skip_non_node(root) return root def parse_with_nodes(self): """ Parse the introspection XML file and build an AST. Returns: An ast.Node instance, representing the root node. If parsing fails, None is returned. """ self._log.clear() root = self._get_root() if root is None: return None root_node = Node.from_xml(root, None, self._log) if root_node.name and \ not Node.is_valid_absolute_object_path(root_node.name): self._log.log_issue('node-name', 'Root node name is not an absolute object ' 'path ‘%s’.' % root_node.name) if self._log.issues: return None return root_node def parse(self): """ Parse the introspection XML file and build an AST. Returns: A non-empty dict of interfaces belonging to the root node in the file, mapping each interface name to an ast.Interface instance. If parsing fails, None is returned. """ root_node = self.parse_with_nodes() interfaces = root_node.interfaces if root_node else None return interfaces dbus-deviation-0.6.0/dbusapi/types.py0000664000175000017500000002120213035124677020402 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 # # Copyright © 2016 Kaloyan Tenchov # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ A representation of the D-Bus type system as a series of classes which can be built into an abstract syntax tree (AST) for representing complex (nested) types. An AST can be built by parsing a D-Bus type signature (using `typeparser.TypeParser`) or by building the tree of objects manually. """ from abc import ABCMeta, abstractmethod # pylint: disable=too-few-public-methods class Type(object): """ An abstract class - AST representation of a D-Bus type. See http://dbus.freedesktop.org/doc/dbus-specification.html#type-system """ __metaclass__ = ABCMeta def __init__(self): """Constructor.""" self.type = "\0" self.name = "INVALID" self.alignment = 1 def __str__(self): """Format the type as a human-readable string.""" return self.type def __eq__(self, other): return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__) def __ne__(self, other): return not self.__eq__(other) # pylint: disable=too-few-public-methods class Byte(Type): """ AST representation of the D-Bus BYTE type. 8-bit unsigned integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "y" self.name = "BYTE" self.alignment = 1 # pylint: disable=too-few-public-methods class Boolean(Type): """ AST representation of the D-Bus BOOLEAN type. Boolean value, 0 is FALSE and 1 is TRUE. Everything else is invalid. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "b" self.name = "BOOLEAN" self.alignment = 4 # pylint: disable=too-few-public-methods class Int16(Type): """ AST representation of the D-Bus INT16 type. 16-bit signed integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "n" self.name = "INT16" self.alignment = 2 # pylint: disable=too-few-public-methods class UInt16(Type): """ AST representation of the D-Bus UINT16 type. 16-bit unsigned integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "q" self.name = "UINT16" self.alignment = 2 # pylint: disable=too-few-public-methods class Int32(Type): """ AST representation of the D-Bus INT32 type. 32-bit signed integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "i" self.name = "INT32" self.alignment = 4 # pylint: disable=too-few-public-methods class UInt32(Type): """ AST representation of the D-Bus UINT32 type. 32-bit unsigned integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "u" self.name = "UINT32" self.alignment = 4 # pylint: disable=too-few-public-methods class Int64(Type): """ AST representation of the D-Bus INT64 type. 64-bit signed integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "x" self.name = "INT64" self.alignment = 8 # pylint: disable=too-few-public-methods class UInt64(Type): """ AST representation of the D-Bus UINT64 type. 64-bit unsigned integer. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "t" self.name = "UINT64" self.alignment = 8 # pylint: disable=too-few-public-methods class Double(Type): """ AST representation of the D-Bus DOUBLE type. IEEE 754 double. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "d" self.name = "DOUBLE" self.alignment = 8 # pylint: disable=too-few-public-methods class String(Type): """ AST representation of the D-Bus STRING type. UTF-8 string (must be valid UTF-8). Must be nul terminated and contain no other nul bytes. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "s" self.name = "STRING" self.alignment = 4 # pylint: disable=too-few-public-methods class ObjectPath(Type): """ AST representation of the D-Bus OBJECT_PATH type. Name of an object instance. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "o" self.name = "OBJECT_PATH" self.alignment = 4 # pylint: disable=too-few-public-methods class Signature(Type): """ AST representation of the D-Bus SIGNATURE type. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "g" self.name = "SIGNATURE" self.alignment = 1 # pylint: disable=too-few-public-methods class Variant(Type): """ AST representation of the D-Bus VARIANT type. Variant type - the type of the value is part of the value itself. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "v" self.name = "VARIANT" self.alignment = 1 # pylint: disable=too-few-public-methods class UnixFD(Type): """ AST representation of the D-Bus UNIX_FD type. Unix file descriptor. """ def __init__(self): """Constructor.""" Type.__init__(self) self.type = "h" self.name = "UNIX_FD" self.alignment = 4 # pylint: disable=too-few-public-methods class Container(Type): """ An abstract class - AST representation of the D-Bus container type. """ __metaclass__ = ABCMeta def __init__(self): """Constructor.""" Type.__init__(self) self.members = [] @abstractmethod def __str__(self): """Format the type as a human-readable string.""" pass # pylint: disable=too-few-public-methods class Array(Container): """ AST representation of the D-Bus ARRAY type. """ def __init__(self): """Constructor.""" Container.__init__(self) self.type = "a" self.name = "ARRAY" self.alignment = 4 def __str__(self): """Format the type as a human-readable string.""" assert len(self.members) == 1 return "{}{}".format(self.type, self.members[0]) # pylint: disable=too-few-public-methods class Struct(Container): """ AST representation of the D-Bus STRUCT type. """ def __init__(self): """Constructor.""" Container.__init__(self) self.type = "r" self.name = "STRUCT" self.alignment = 8 def __str__(self): """Format the type as a human-readable string.""" return "({})".format("".join(map(str, self.members))) # pylint: disable=too-few-public-methods class DictEntry(Container): """ AST representation of the D-Bus DICT_ENTRY type. """ def __init__(self): """Constructor.""" Container.__init__(self) self.type = "e" self.name = "DICT_ENTRY" self.alignment = 8 def __str__(self): """Format the type as a human-readable string.""" assert len(self.members) == 2 return "{{{}{}}}".format(self.members[0], self.members[1]) # pylint: disable=too-few-public-methods class TypeSignature(object): """ AST representation of a D-Bus signature - an ordered list of one or more types. See http://dbus.freedesktop.org/doc/dbus-specification.html#type-system """ def __init__(self): """Constructor.""" self.members = [] def __str__(self): """Format the type as a human-readable string.""" return "".join(map(str, self.members)) def __eq__(self, other): return (isinstance(other, self.__class__) and self.__dict__ == other.__dict__) def __ne__(self, other): return not self.__eq__(other) dbus-deviation-0.6.0/dbusapi/typeparser.py0000664000175000017500000001545613035125005021434 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 # # Copyright © 2016 Kaloyan Tenchov # Copyright © 2017 Philip Withnall # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Module providing a `TypeParser` object for parsing D-Bus type strings into abstract syntax trees (ASTs) representing the nested type structure. """ from dbusapi import types from dbusapi.log import Log class TypeParsingLog(Log): """Specialized Log subclass for type parsing messages""" def __init__(self): """Construct a new TypeParsingLog""" super(TypeParsingLog, self).__init__() self.register_issue_code('invalid-type') self.register_issue_code('reserved-type') self.register_issue_code('unknown-type') self.domain = 'types' class TypeParser(object): """ Parse and validate a D-Bus type string. The D-Bus type system and type strings are explained in detail in the D-Bus specification: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system and in the GVariant documentation for GLib: https://developer.gnome.org/glib/stable/glib-GVariantType.html#id-1.6.18.6.9 """ def __init__(self, signature): """ Construct a new TypeParser. Args: signature: a D-Bus type string """ self.signature = signature self._log = TypeParsingLog() self._index = 0 @staticmethod def get_output_codes(): """Return a list of all possible output codes.""" return TypeParsingLog().issue_codes def get_output(self): """Return a list of all logged parser messages.""" return self._log.issues def _get_next_character(self): """Return the next character from the signature.""" if self._index < len(self.signature): character = self.signature[self._index] self._index += 1 else: character = None return character # pylint: disable=too-many-return-statements,too-many-branches def _parse_one_type(self, character): """Parse one complete type from the signature.""" basic_types = { 'y': types.Byte, 'b': types.Boolean, 'n': types.Int16, 'q': types.UInt16, 'i': types.Int32, 'u': types.UInt32, 'x': types.Int64, 't': types.UInt64, 'd': types.Double, 's': types.String, 'o': types.ObjectPath, 'g': types.Signature, 'v': types.Variant, 'h': types.UnixFD, } if character in basic_types: return basic_types[character]() elif character == "a": out_array = types.Array() character = self._get_next_character() if not character: self._log.log_issue('invalid-type', 'Incomplete array declaration.') return None one_type = self._parse_one_type(character) if not one_type: # Invalid member type - error has already been logged. return None out_array.members.append(one_type) return out_array elif character == "(": out_struct = types.Struct() while True: character = self._get_next_character() if not character or \ (character == ')' and len(out_struct.members) == 0): self._log.log_issue('invalid-type', 'Incomplete structure declaration.') return None elif character == ")": break one_type = self._parse_one_type(character) if not one_type: # Invalid member type - error has already been logged. return None out_struct.members.append(one_type) return out_struct elif character == "{": out_dict = types.DictEntry() while True: character = self._get_next_character() if not character or \ (character == '}' and len(out_dict.members) != 2): self._log.log_issue('invalid-type', 'Incomplete dictionary declaration.') return None elif character == "}": break one_type = self._parse_one_type(character) if not one_type: # Invalid member type - error has already been logged. return None if len(out_dict.members) >= 2: self._log.log_issue('invalid-type', 'Invalid dictionary declaration.') return None out_dict.members.append(one_type) return out_dict elif character in ['r', 'e', 'm', '*', '?', '@', '&', '^']: # https://dbus.freedesktop.org/doc/dbus-specification.html#idm399 self._log.log_issue('reserved-type', 'Reserved type ‘%s’ must not be used in ' 'signatures on D-Bus.' % character) return None else: self._log.log_issue('unknown-type', 'Unknown type ‘%s’.' % character) return None def parse(self): """ Parse the type string and build an AST of the type Returns: A non-empty list of types. If parsing fails, or if the input string is empty, None is returned. """ self._log.clear() self._index = 0 if len(self.signature) == 0: self._log.log_issue('invalid-type', 'Empty type string.') out = types.TypeSignature() while True: character = self._get_next_character() if not character: break one_type = self._parse_one_type(character) if not one_type: # Invalid type break out.members.append(one_type) if self._log.issues: return None return out dbus-deviation-0.6.0/dbusapi/__init__.py0000664000175000017500000000000013026466535020770 0ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusapi/log.py0000664000175000017500000000336313026466535020031 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # # Copyright © 2015, 2016 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ An implementation of a base logging class. """ class Log(object): """Base logging class.""" def __init__(self): """Construct a new Log""" self.issues = [] self.issue_codes = set() self.domain = 'default' def register_issue_code(self, code): """ Register a new issue code. Duplicate codes will be silently ignored. Args: code: str, an issue code, for example `unknown-node` """ self.issue_codes.add(code) def log_issue(self, code, message): """ Log a new issue. Args: code: str, A registered code for that issue. message: str, A message describing the issue. """ assert code in self.issue_codes self.issues.append(self._create_entry(code, message)) # pylint: disable=no-self-use def _create_entry(self, code, message): return None, self.domain, code, message def clear(self): """Clear the issue list.""" self.issues = [] dbus-deviation-0.6.0/dbusapi/typeformatter.py0000664000175000017500000000764613035744274022164 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 # # Copyright © 2017 Philip Withnall # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Module providing a `BasicTypeFormatter` object for formatting D-Bus type abstract syntax trees (ASTs) into human-readable strings describing the type structure. These strings are intended to be easy to read for a technical reader, but without having to memorise the single-character D-Bus types. They could be used in documentation, for example. Other type formatters may be added in future which format the type ASTs differently. """ from dbusapi import types class BasicTypeFormatter(object): """ Format a D-Bus type AST as a string. Given a valid D-Bus type structure (see types.Type and types.TypeSignature), format it as a human-readable string. The format mirrors that used by D-Feet when introspecting D-Bus interfaces. In future, support may be added to this class for choosing the output language for the formatted strings. """ def format(self, type_instance): """ Format the type instance as a human-readable string. Note that the type instance must be a valid type structure. For example, this means that any types.Struct instance must have one or more child members; any types.DictEntry instance must have exactly two child members (key and value) and be the immediate child of a types.Array instance. See the D-Bus documentation for the full details of type system restrictions: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system The type instance may be a types.Type instance or a types.TypeSignature instance. Args: type_instance: valid types.Type or types.TypeSignature instance """ basic_type_map = { types.Byte: 'Byte', types.Boolean: 'Boolean', types.Int16: 'Int16', types.UInt16: 'UInt16', types.Int32: 'Int32', types.UInt32: 'UInt32', types.Int64: 'Int64', types.UInt64: 'UInt64', types.Double: 'Double', types.String: 'String', types.ObjectPath: 'Object Path', types.Signature: 'Signature', types.Variant: 'Variant', types.UnixFD: 'Unix FD', } # pylint: disable=unidiomatic-typecheck if isinstance(type_instance, types.TypeSignature): return ', '.join(map(self.format, type_instance.members)) elif (isinstance(type_instance, types.Array) and isinstance(type_instance.members[0], types.DictEntry)): dictionary = type_instance.members[0] key = self.format(dictionary.members[0]) value = self.format(dictionary.members[1]) return 'Dict of {{{}: {}}}'.format(key, value) elif isinstance(type_instance, types.Array): member = self.format(type_instance.members[0]) return 'Array of [{}]'.format(member) elif isinstance(type_instance, types.Struct): members = map(self.format, type_instance.members) return 'Struct of ({})'.format(', '.join(members)) elif type(type_instance) in basic_type_map: return basic_type_map[type(type_instance)] else: assert False dbus-deviation-0.6.0/dbusapi/ast.py0000664000175000017500000005652413035123314020027 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # # Copyright © 2015, 2016 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ An implementation of the abstract syntax tree (AST) for a D-Bus introspection document, which fully describes a D-Bus API. An AST can be built by parsing an XML file (using `interfaceparser.InterfaceParser`) or by building the tree of objects manually. """ from abc import ABCMeta from collections import OrderedDict # pylint: disable=no-member from lxml import etree from re import match from dbusapi.log import Log from dbusapi.typeparser import TypeParser TP_DTD = 'http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0' FDO_DTD = 'http://www.freedesktop.org/dbus/1.0/doc.dtd' class AstLog(Log): """Specialized Log subclass for AST messages""" def __init__(self): """Construct a new AstLog""" super(AstLog, self).__init__() self.register_issue_code('unknown-node') self.register_issue_code('empty-root') self.register_issue_code('missing-attribute') self.register_issue_code('duplicate-node') self.register_issue_code('duplicate-interface') self.register_issue_code('duplicate-method') self.register_issue_code('duplicate-signal') self.register_issue_code('duplicate-property') self.register_issue_code('node-name') self.register_issue_code('interface-name') self.register_issue_code('method-name') self.register_issue_code('signal-name') self.register_issue_code('property-type') self.register_issue_code('argument-type') self.domain = 'ast' def ignore_node(node): """Decide whether to ignore the given node when parsing.""" return node.tag[0] == '{' # in a namespace # pylint: disable=too-many-instance-attributes class BaseNode(object): """Base class for all D-Bus AST nodes.""" __metaclass__ = ABCMeta DOCSTRING_TAGS = ['{%s}docstring' % TP_DTD, '{%s}doc' % FDO_DTD] required_attributes = ['name'] optional_attributes = [] def __init__(self, name, annotations=None, log=None): """Construct a new ast.BaseNode Args: name: str, the name of the node. annotations: potentially empty dict of annotations applied to the node, mapping annotation name to an `ast.Annotation` instance log: subclass of `Log`, used to store log messages; can be None """ self.name = name self.children = [] self.parent = None self._comment = None self.log = log or AstLog() self.annotations = OrderedDict() self.line_number = -1 self.comment_line_number = -1 self._children_types = { 'annotation': Annotation, } self._type_containers = { Annotation: self.annotations, } for annotation in (annotations or {}).values(): self._add_child(annotation) @classmethod def from_xml(cls, node, comment, log, parent=None): """Return a new ast.BaseNode instance from an XML node.""" attrs = {} valid = True for attr_name in cls.required_attributes: # Avoid redefining a builtin member_name = attr_name if member_name == 'type': member_name = 'type_' try: attrs[member_name] = node.attrib[attr_name] except KeyError: log.log_issue('missing-attribute', 'Missing required attribute ‘%s’ in %s.' % (attr_name, node.tag)) valid = False if not valid: return None for attr_name in cls.optional_attributes: attrs[attr_name] = node.attrib.get(attr_name) # FIXME: Hack for the fact that Node.name and Argument.name are not # actually required, but is the first attribute in the constructor, and # hence must be specified. This can be removed when we break API. if (cls == Node or cls == Argument) and 'name' not in attrs: attrs['name'] = None elif issubclass(cls, Callable) and 'args' not in attrs: attrs['args'] = [] attrs['log'] = log res = cls(**attrs) res.line_number = node.sourceline if comment is not None: res.comment = comment.text # lxml reports the last source line for xml comments as # being the actual source line, fix this. # Also report line numbers starting from 1, consistent # with node.line_number res.comment_line_number = (comment.sourceline - len(res.comment.split('\n')) + 1) if parent: parent.add_child(res) res.parse_xml_children(node) return res def add_child(self, child): """Add a child to the node""" return self._add_child(child) def parse_xml_children(self, node): """Parse the XML node's children.""" xml_comment = None for elem in node: if elem.tag == etree.Comment: xml_comment = elem continue elif elem.tag in BaseNode.DOCSTRING_TAGS: self.comment_line_number = elem.sourceline self.comment = elem.text continue elif ignore_node(elem): xml_comment = None continue try: ctype = self._children_types[elem.tag] except KeyError: xml_comment = None if isinstance(self, Node) and not self.pretty_name: # Special handling for root nodes to allow more meaningful # error messages. self.__log_issue('unknown-node', "Unknown node ‘%s’ in root." % elem.tag) else: self.__log_issue('unknown-node', "Unknown node ‘%s’ in %s ‘%s’." % (elem.tag, type(self).__name__.lower(), self.pretty_name)) continue ctype.from_xml(elem, xml_comment, parent=self, log=self.log) xml_comment = None def walk(self): """Traverse this node's children in pre-order.""" for child in self.children: yield child for grandchild in child.walk(): yield grandchild # Backward compat def format_name(self): """Format this node's name as a human-readable string""" return self.pretty_name @property def comment(self): """ Get the comment for this node. Returns: str: If the node was annotated with `org.gtk.GDBus.DocString`, the value of the annotation, otherwise one of: * A tp:docstring child node * A doc:doc child node * An XML comment immediately preceding the XML node. , whichever is seen last. """ try: doc_annotation = self.annotations['org.gtk.GDBus.DocString'] return doc_annotation.value except KeyError: return self._comment @comment.setter def comment(self, value): """Set the comment for this node.""" self._comment = value @property def pretty_name(self): """Format the node's name as a human-readable string.""" return self.name def __log_issue(self, code, message): self.log.log_issue(code, message) def _child_is_duplicate(self, child): return child.name in self._type_containers[type(child)] def _add_child(self, child): child.parent = self if self._child_is_duplicate(child): self.__log_issue('duplicate-%s' % type(child).__name__.lower(), 'Duplicate %s definition ‘%s’.' % (type(child).__name__.lower(), child.pretty_name)) return False self.children.append(child) container = self._type_containers[type(child)] if isinstance(container, list): container.append(child) else: container[child.name] = child return True class Node(BaseNode): """ AST representation of a element. This represents the top level of a D-Bus API. """ required_attributes = [] optional_attributes = ['name'] # pylint: disable=too-many-arguments def __init__(self, name=None, interfaces=None, nodes=None, annotations=None, log=None): """ Construct a new ast.Node. Args: name: node name; a non-empty string; The root should either have no name or should have a name that is a valid absolute object path. Child names must be valid relative paths. interfaces: potentially empty dict of interfaces in the node, mapping interface name to an `ast.Interface` instance nodes: potentially empty dict of properties in the node, mapping node name to an `ast.Node` instance annotations: potentially empty dict of annotations applied to the node, mapping annotation name to an `ast.Annotation` instance log: subclass of `Log`, used to store log messages; can be None """ super(Node, self).__init__(name, annotations, log) self._children_types.update({'interface': Interface, 'node': Node}) self.interfaces = OrderedDict() self.nodes = OrderedDict() self._type_containers.update({Interface: self.interfaces, Node: self.nodes}) for child in (interfaces or {}).values(): self._add_child(child) for child in (nodes or {}).values(): self._add_child(child) def _add_child(self, child): if isinstance(child, Node): if not child.name: self.log.log_issue('missing-attribute', 'Missing required attribute ‘name’ in ' 'non-root node.') elif not Node.is_valid_relative_object_path(child.name): self.log.log_issue('node-name', 'Non-root node name is not a relative ' 'object path ‘%s’.' % child.name) child.node = self return super(Node, self)._add_child(child) @staticmethod def is_valid_absolute_object_path(path): """ Validate an absolute D-Bus object path. https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path Args: path: object path """ return path == '/' or match(r'(/[A-Za-z0-9_]+)+', path) is not None @staticmethod def is_valid_relative_object_path(path): """ Validate a relative D-Bus object path. https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path Args: path: object path """ return match(r'[A-Za-z0-9_]+(/[A-Za-z0-9_]+)*', path) is not None class Interface(BaseNode): """ AST representation of an element. This represents the most commonly used node of a D-Bus API. """ # pylint: disable=too-many-arguments def __init__(self, name, methods=None, properties=None, signals=None, annotations=None, log=None): """ Construct a new ast.Interface. Args: name: interface name; a non-empty string methods: potentially empty dict of methods in the interface, mapping method name to an `ast.Method` instance properties: potentially empty dict of properties in the interface, mapping property name to an `ast.Property` instance signals: potentially empty dict of signals in the interface, mapping signal name to an `ast.Signal` instance annotations: potentially empty dict of annotations applied to the interface, mapping annotation name to an `ast.Annotation` instance log: subclass of `Log`, used to store log messages; can be None """ super(Interface, self).__init__(name, annotations, log) if name and not Interface.is_valid_interface_name(name): self.log.log_issue('interface-name', 'Invalid interface name ‘%s’.' % name) self._children_types.update({'signal': Signal, 'method': Method, 'property': Property}) self.methods = OrderedDict() self.signals = OrderedDict() self.properties = OrderedDict() self._type_containers.update({Method: self.methods, Signal: self.signals, Property: self.properties}) for child in (methods or {}).values(): self._add_child(child) for child in (signals or {}).values(): self._add_child(child) for child in (properties or {}).values(): self._add_child(child) def _add_child(self, child): child.interface = self return super(Interface, self)._add_child(child) @staticmethod def is_valid_interface_name(name): """ Validate a D-Bus interface name. http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface Args: name: interface name """ return len(name) <= 255 and \ match(r'[A-Za-z_][A-Za-z0-9_]*' r'(\.[A-Za-z_][A-Za-z0-9_]*)+', name) is not None def _dotted_name(elem): if elem.parent: return elem.parent.format_name() + '.' + elem.name return elem.name class Property(BaseNode): """ AST representation of a element. This represents a readable or writable property of an interface. """ ACCESS_READ = 'read' ACCESS_WRITE = 'write' ACCESS_READWRITE = 'readwrite' required_attributes = BaseNode.required_attributes + ['access', 'type'] # pylint: disable=too-many-arguments def __init__(self, name, type_, access, annotations=None, log=None): """ Construct a new ast.Property. Args: name: property name; a non-empty string, not including the parent interface name type_: type string for the property; see http://goo.gl/uCpa5A access: ACCESS_READ, ACCESS_WRITE, or ACCESS_READWRITE annotations: potentially empty dict of annotations applied to the property, mapping annotation name to an `ast.Annotation` instance log: subclass of `Log`, used to store log messages; can be None """ super(Property, self).__init__(name, annotations, log) type_parser = TypeParser(type_) self.type = type_parser.parse() if self.type is None: message = type_parser.get_output()[0][3] self.log.log_issue('property-type', 'Error when parsing type ‘%s’ for property ' '‘%s’: %s' % (type_, name, message)) self.access = access self.interface = None @property def pretty_name(self): """Format the property's name as a human-readable string""" return _dotted_name(self) class Callable(BaseNode): u""" AST representation of a callable element. This represents a ‘callable’, such as a method or a signal. All callables contain a list of in and out arguments. """ def __init__(self, name, args, annotations=None, log=None): """ Construct a new ast.Callable. Args: name: callable name; a non-empty string, not including the parent interface name args: potentially empty ordered list of ast.Arguments accepted and returned by the callable annotations: potentially empty dict of annotations applied to the callable, mapping annotation name to an ast.Annotation instance log: subclass of `Log`, used to store log messages; can be None """ super(Callable, self).__init__(name, annotations, log) self.arguments = [] self.interface = None self._children_types.update({'arg': Argument}) self._type_containers.update({Argument: self.arguments}) for arg in args: self._add_child(arg) def _child_is_duplicate(self, child): if isinstance(child, Argument): return False return super(Callable, self)._child_is_duplicate(child) @property def pretty_name(self): """Format the callable's name as a human-readable string""" return _dotted_name(self) @staticmethod def is_valid_name(name): """ Validate a D-Bus member name. https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-member Args: name: callable name """ return len(name) <= 255 and \ match(r'[A-Za-z_][A-Za-z0-9_]*', name) is not None class Method(Callable): """ AST representation of a element. This represents a callable method of an interface. """ def __init__(self, name, args, annotations=None, log=None): """ Construct a new ast.Method. Args: name: method name; a non-empty string, not including the parent interface name args: potentially empty ordered list of ast.Arguments accepted and returned by the method annotations: potentially empty dict of annotations applied to the method, mapping annotation name to an ast.Annotation instance log: subclass of `Log`, used to store log messages; can be None """ super(Method, self).__init__(name, args, annotations, log) if name and not Callable.is_valid_name(name): self.log.log_issue('method-name', 'Invalid method name ‘%s’.' % name) class Signal(Callable): """ AST representation of a element. This represents an emittable signal on an interface. """ def __init__(self, name, args, annotations=None, log=None): """ Construct a new ast.Signal. Args: name: signal name; a non-empty string, not including the parent interface name args: potentially empty ordered list of ast.Arguments accepted and returned by the signal annotations: potentially empty dict of annotations applied to the signal, mapping annotation name to an ast.Annotation instance log: subclass of `Log`, used to store log messages; can be None """ super(Signal, self).__init__(name, args, annotations, log) if name and not Callable.is_valid_name(name): self.log.log_issue('signal-name', 'Invalid signal name ‘%s’.' % name) class Argument(BaseNode): """ AST representation of an element. This represents an argument to an `ast.Signal` or `ast.Method`. """ DIRECTION_IN = 'in' DIRECTION_OUT = 'out' required_attributes = ['type'] optional_attributes = ['direction', 'name'] # pylint: disable=too-many-arguments def __init__(self, name, direction, type_, annotations=None, log=None): """ Construct a new ast.Argument. Args: name: argument name; may be empty direction: DIRECTION_IN or DIRECTION_OUT type_: type string for the argument; see http://goo.gl/uCpa5A annotations: potentially empty dict of annotations applied to the argument, mapping annotation name to an ast.Annotation instance log: subclass of `Log`, used to store log messages; can be None """ super(Argument, self).__init__(name, annotations, log) type_parser = TypeParser(type_) self.type = type_parser.parse() if self.type is None: message = type_parser.get_output()[0][3] self.log.log_issue('argument-type', 'Error when parsing type ‘%s’ for argument ' '‘%s’: %s' % (type_, name, message)) self.direction = direction or Argument.DIRECTION_IN self._index = -1 @property def pretty_name(self): """Format the argument's name as a human-readable string""" if self.index == -1 and self.name is None: res = 'unnamed' elif self.index == -1: res = '‘%s’' % self.name elif self.name is None: res = '%u' % self.index else: res = '%u (‘%s’)' % (self.index, self.name) if self.parent: parent_type = type(self.parent).__name__.lower() res += ' of %s ‘%s’' % (parent_type, self.parent.pretty_name) return res @property def index(self): """The index of this argument in its parent's list of arguments""" # Slight optimization, assumes arguments cannot be reparented if self._index != -1: return self._index if not self.parent: return -1 else: self._index = self.parent.arguments.index(self) return self._index class Annotation(BaseNode): """ AST representation of an element. This represents an arbitrary key-value metadata annotation attached to one of the nodes in an interface. The annotation name can be one of the well-known ones described at http://goo.gl/LgmNUe, or could be something else. """ optional_attributes = ['value'] def __init__(self, name, value=None, log=None): """ Construct a new ast.Annotation. Args: name: annotation name; a non-empty string value: annotation value; any string is permitted log: subclass of `Log`, used to store log messages; can be None """ super(Annotation, self).__init__(name, log=log) self.value = value @property def pretty_name(self): """Format the annotation's name as a human-readable string""" if not self.parent: return self.name return '%s of ‘%s’' % (self.name, self.parent.pretty_name) dbus-deviation-0.6.0/dbusapi/tests/0000775000175000017500000000000013035747236020033 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusapi/tests/test_typeformatter.py0000664000175000017500000001110313035744374024345 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 # # Copyright © 2017 Philip Withnall # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Unit tests for dbusapi.typeformatter """ from dbusapi.typeformatter import BasicTypeFormatter from dbusapi import types import unittest class TestBasicFormatterNormal(unittest.TestCase): """Test normal formatting of types with the BasicTypeFormatter.""" # pylint: disable=invalid-name def assertFormat(self, type_instance, formatted): # noqa formatter = BasicTypeFormatter() self.assertEqual(formatter.format(type_instance), formatted) def test_basic_types(self): basic_type_map = { types.Byte: 'Byte', types.Boolean: 'Boolean', types.Int16: 'Int16', types.UInt16: 'UInt16', types.Int32: 'Int32', types.UInt32: 'UInt32', types.Int64: 'Int64', types.UInt64: 'UInt64', types.Double: 'Double', types.String: 'String', types.ObjectPath: 'Object Path', types.Signature: 'Signature', types.Variant: 'Variant', types.UnixFD: 'Unix FD', } for k, v in basic_type_map.items(): self.assertFormat(k(), v) def test_array(self): type_instance = types.Array() type_instance.members.append(types.Byte()) self.assertFormat(type_instance, 'Array of [Byte]') def test_struct(self): type_instance = types.Struct() type_instance.members.append(types.String()) type_instance.members.append(types.String()) type_instance.members.append(types.UInt16()) self.assertFormat(type_instance, 'Struct of (String, String, UInt16)') def test_dict(self): dict_instance = types.DictEntry() dict_instance.members.append(types.String()) dict_instance.members.append(types.Variant()) array_instance = types.Array() array_instance.members.append(dict_instance) self.assertFormat(array_instance, 'Dict of {String: Variant}') def test_invalid_dict(self): """Test that a bare DictEntry (not in an Array) is not formatted""" dict_instance = types.DictEntry() dict_instance.members.append(types.String()) dict_instance.members.append(types.Variant()) formatter = BasicTypeFormatter() with self.assertRaises(AssertionError): formatter.format(dict_instance) def test_something_long(self): type_signature = types.TypeSignature() type_signature.members.append(types.Int32()) type_signature.members.append(types.Int32()) type_signature.members.append(types.Double()) type_signature.members.append(types.Double()) type_signature.members.append(types.Double()) struct = types.Struct() struct.members.append(types.Int32()) struct.members.append(types.Double()) struct.members.append(types.Double()) type_signature.members.append(struct) self.assertFormat(type_signature, 'Int32, Int32, Double, Double, Double, ' 'Struct of (Int32, Double, Double)') def test_nested_structs(self): s1 = types.Struct() s1.members.append(types.Int32()) s1.members.append(types.Int32()) s2 = types.Struct() s2.members.append(types.UInt32()) s2.members.append(types.UInt32()) s3 = types.Struct() s3.members.append(s1) s3.members.append(s2) self.assertFormat(s3, 'Struct of (' 'Struct of (Int32, Int32), ' 'Struct of (UInt32, UInt32))') def test_type_signature(self): signature = types.TypeSignature() signature.members.append(types.String()) signature.members.append(types.UInt64()) self.assertFormat(signature, 'String, UInt64') if __name__ == '__main__': # Run test suite unittest.main() dbus-deviation-0.6.0/dbusapi/tests/test_typeparser.py0000664000175000017500000001354713035123314023635 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # vim: ai ts=4 sts=4 et sw=4 # # Copyright © 2016 Kaloyan Tenchov # Copyright © 2017 Philip Withnall # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Unit tests for dbusapi.typeparser """ from dbusapi.typeparser import TypeParser import unittest def _test_parser(signature): """Build a TypeParser for a signature and parse it.""" parser = TypeParser(signature) types = parser.parse() return parser, types class TestParserErrors(unittest.TestCase): """Test error handling in the TypeParser.""" # pylint: disable=invalid-name def assertOutput(self, signature, partial_output): # noqa (parser, types) = _test_parser(signature) self.assertEqual(types, None) actual_output = \ [(None, 'types', i[0], i[1]) for i in partial_output] self.assertEqual(parser.get_output(), actual_output) def test_reserved(self): """ Type code 114 'r' is reserved for use in bindings and implementations to represent the general concept of a struct, and must not appear in signatures used on D-Bus. Similarly for the other type strings here. https://dbus.freedesktop.org/doc/dbus-specification.html#idm399 """ vectors = [ 'r', 'e', 'm', '*', '?', '@', '&', '^' ] for type_string in vectors: # Unfortunately we can’t use TestCase.subTest here until we depend # on Python 3.4 at a minimum. self.assertOutput(type_string, [ ('reserved-type', 'Reserved type ‘%s’ must not be used in signatures on ' 'D-Bus.' % type_string), ]) def test_incomplete(self): self.assertOutput( "aa", [ ('invalid-type', 'Incomplete array declaration.'), ]) def test_incomplete2(self): self.assertOutput( "(ii", [ ('invalid-type', 'Incomplete structure declaration.'), ]) def test_incomplete3(self): self.assertOutput( "ii)", [ ('unknown-type', 'Unknown type ‘)’.'), ]) def test_incomplete4(self): self.assertOutput( "a{suu}", [ ('invalid-type', 'Invalid dictionary declaration.'), ]) def test_incomplete5(self): self.assertOutput( "a{su", [ ('invalid-type', 'Incomplete dictionary declaration.'), ]) def test_incomplete6(self): self.assertOutput( "a{s", [ ('invalid-type', 'Incomplete dictionary declaration.'), ]) def test_invalid_array(self): self.assertOutput( 'a!', [ ('unknown-type', 'Unknown type ‘!’.'), ] ) def test_empty(self): self.assertOutput( "", [ ('invalid-type', 'Empty type string.'), ] ) def test_empty_tuple(self): self.assertOutput( '()', [ ('invalid-type', 'Incomplete structure declaration.'), ] ) def test_invalid_tuple(self): self.assertOutput( '(!', [ ('unknown-type', 'Unknown type ‘!’.'), ] ) def test_empty_dict(self): self.assertOutput( '{}', [ ('invalid-type', 'Incomplete dictionary declaration.'), ] ) def test_invalid_dict(self): self.assertOutput( '{!', [ ('unknown-type', 'Unknown type ‘!’.'), ] ) def test_underfilled_dict(self): self.assertOutput( '{s}', [ ('invalid-type', 'Incomplete dictionary declaration.'), ] ) def test_overfilled_dict(self): self.assertOutput( '{sss}', [ ('invalid-type', 'Invalid dictionary declaration.'), ] ) class TestParserNormal(unittest.TestCase): """Test normal parsing of unusual input in the TypeParser.""" # pylint: disable=invalid-name def assertParse(self, signature): # noqa (parser, type_signature) = _test_parser(signature) self.assertEqual(parser.get_output(), []) actual_signature = str(type_signature) self.assertEqual(signature, actual_signature) def test_parse_valid(self): vectors = [ 'y', 'b', 'n', 'q', 'i', 'ii', 'aiai', 'u', 'uu', 'x', 't', 'd', 's', 'o', 'g', 'v', 'h', 'ai', 'aai', 'au', 'av', '(iii)', '(ii)(ii)', '(i(ii))', '(v)', '(ius)', 'a(ii)', 'a{us}', 'a{us}i' ] for type_string in vectors: # Unfortunately we can’t use TestCase.subTest here until we depend # on Python 3.4 at a minimum. self.assertParse(type_string) class TestParserOutputCodes(unittest.TestCase): """Test the output codes from TypeParser.""" def test_unique(self): codes = TypeParser.get_output_codes() self.assertEqual(len(codes), len(set(codes))) def test_non_empty(self): codes = TypeParser.get_output_codes() self.assertNotEqual(codes, []) if __name__ == '__main__': # Run test suite unittest.main() dbus-deviation-0.6.0/dbusapi/tests/test_interfaceparser.py0000775000175000017500000006215313035123314024614 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Unit tests for dbusapi.interfaceparser """ # pylint: disable=missing-docstring from dbusapi.interfaceparser import InterfaceParser import os import tempfile import unittest def _create_temp_xml_file(xml): """Create a temporary XML file with the given contents.""" tmp_fd, tmp_name = tempfile.mkstemp(suffix='.xml', text=True) xml_fd = os.fdopen(tmp_fd, 'wt') xml_fd.write(xml) xml_fd.close() return tmp_name def _test_parser_with_nodes(xml): """Build an InterfaceParser for the XML snippet and parse it.""" tmpfile = _create_temp_xml_file(xml) parser = InterfaceParser(tmpfile) root_node = parser.parse_with_nodes() os.unlink(tmpfile) return parser, root_node, tmpfile def _test_parser(xml): """Build an InterfaceParser for the XML snippet and parse it.""" parser, root_node, tmpfile = _test_parser_with_nodes(xml) interfaces = root_node.interfaces if root_node else None return parser, interfaces, tmpfile # pylint: disable=too-many-public-methods class TestParserErrors(unittest.TestCase): """Test error handling in the InterfaceParser.""" # pylint: disable=invalid-name def assertOutputWithNodes(self, xml, partial_output): # noqa (parser, root_node, filename) = _test_parser_with_nodes(xml) self.assertEqual(root_node, None) actual_output = \ [(filename, 'parser', i[0], i[1]) for i in partial_output] self.assertEqual(parser.get_output(), actual_output) # pylint: disable=invalid-name def assertOutput(self, xml, partial_output): # noqa (parser, interfaces, filename) = _test_parser(xml) self.assertEqual(interfaces, None) actual_output = \ [(filename, 'parser', i[0], i[1]) for i in partial_output] self.assertEqual(parser.get_output(), actual_output) def test_unknown_root_node(self): self.assertOutput( "", [ ('unknown-node', 'Unknown root node ‘notnode’.'), ]) def test_nonabsolute_node_name(self): self.assertOutputWithNodes( "", [ ('node-name', 'Root node name is not an absolute object path ‘rel/N’.'), ]) def test_invalid_node_name(self): self.assertOutputWithNodes( "", [ ('node-name', 'Root node name is not an absolute object path ‘//’.'), ]) def test_missing_node_name(self): self.assertOutputWithNodes( "", [ ('missing-attribute', 'Missing required attribute ‘name’ in non-root node.'), ]) def test_nonrelative_node_name(self): self.assertOutputWithNodes( "", [ ('node-name', 'Non-root node name is not a relative object path ‘/abs/N’.'), ]) def test_duplicate_node(self): self.assertOutputWithNodes( "", [ ('duplicate-node', 'Duplicate node definition ‘N’.'), ]) def test_invalid_interface_name(self): self.assertOutput( "", [ ('interface-name', 'Invalid interface name ‘0’.'), ]) def test_duplicate_interface(self): self.assertOutput( "", [ ('duplicate-interface', 'Duplicate interface definition ‘I.I’.'), ]) def test_unknown_interface_node(self): self.assertOutput( "", [ ('unknown-node', 'Unknown node ‘badnode’ in root.'), ]) def test_interface_missing_name(self): self.assertOutput( "", [ ('missing-attribute', 'Missing required attribute ‘name’ in interface.'), ]) def test_invalid_method(self): self.assertOutput( "" "" "", [ ('method-name', 'Invalid method name ‘0M’.'), ]) def test_duplicate_method(self): self.assertOutput( "" "" "", [ ('duplicate-method', 'Duplicate method definition ‘I.I.M’.'), ]) def test_invalid_signal(self): self.assertOutput( "" "" "", [ ('signal-name', 'Invalid signal name ‘*S’.'), ]) def test_duplicate_signal(self): self.assertOutput( "" "" "", [ ('duplicate-signal', 'Duplicate signal definition ‘I.I.S’.'), ]) def test_invalid_signal_signature(self): self.assertOutput( "" "" "", [ ('argument-type', 'Error when parsing type ‘?’ for argument ‘N’: ' 'Reserved type ‘?’ must not be used in signatures on D-Bus.'), ]) def test_duplicate_property(self): self.assertOutput( "" "" "" "", [ ('duplicate-property', 'Duplicate property definition ‘I.I.P’.'), ]) def test_invalid_property_signature(self): self.assertOutput( "" "" "", [ ('property-type', 'Error when parsing type ‘a?’ for property ‘P’: ' 'Reserved type ‘?’ must not be used in signatures on D-Bus.'), ]) def test_unknown_sub_interface_node(self): self.assertOutput( "", [ ('unknown-node', 'Unknown node ‘badnode’ in interface ‘I.I’.'), ]) def test_method_missing_name(self): self.assertOutput( "", [ ('missing-attribute', 'Missing required attribute ‘name’ in method.'), ]) def test_unknown_method_node(self): self.assertOutput( "" "" "", [ ('unknown-node', 'Unknown node ‘badnode’ in method ‘I.I.M’.'), ]) def test_signal_missing_name(self): self.assertOutput( "", [ ('missing-attribute', 'Missing required attribute ‘name’ in signal.'), ]) def test_unknown_signal_node(self): self.assertOutput( "" "" "", [ ('unknown-node', 'Unknown node ‘badnode’ in signal ‘I.I.S’.'), ]) def test_property_missing_name(self): self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in property.'), ]) def test_property_missing_type(self): self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘type’ in property.'), ]) def test_property_missing_access(self): self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘access’ in property.'), ]) def test_unknown_property_node(self): self.assertOutput( "" "" "" "" "", [ ('unknown-node', 'Unknown node ‘badnode’ in property ‘I.I.P’.'), ]) def test_unknown_arg_node(self): self.assertOutput( "" "" "", [ ('unknown-node', 'Unknown node ‘badnode’ in argument ‘0 of method ‘I.I.M’’.'), ]) def test_arg_missing_type(self): self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘type’ in arg.'), ]) def test_annotation_missing_name(self): self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ]) def test_unknown_annotation_node(self): self.assertOutput( "" "" "", [ ('unknown-node', 'Unknown node ‘badnode’ in annotation ‘N of ‘I.I’’.'), ]) # pylint: disable=too-many-public-methods class TestParserNormal(unittest.TestCase): """Test normal parsing of unusual input in the InterfaceParser.""" # pylint: disable=invalid-name def assertParse(self, xml): # noqa (parser, interfaces, _) = _test_parser(xml) self.assertEqual(parser.get_output(), []) return interfaces def test_tp_spec_root(self): """Test that specifications wrapped in tp:spec are parsed.""" self.assertParse( "") def test_ignored_namespaced_tags_interface(self): self.assertParse( "" "") def test_ignored_namespaced_tags(self): self.assertParse( "" "" "" "" "") def test_doc_root(self): """Test that doc tags are ignored in the root.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_interface(self): """Test that doc tags are ignored in interfaces.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_method(self): """Test that doc tags are ignored in methods.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_signal(self): """Test that doc tags are ignored in signals.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_property(self): """Test that doc tags are ignored in properties.""" self.assertParse( "" "" "Ignore me." "Ignore me." "") def test_doc_arg(self): """Test that doc tags are ignored in args.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_annotation(self): """Test that doc tags are ignored in annotations.""" self.assertParse( "" "Ignore me." "Ignore me." "") def test_doc_comments(self): """Test that xml comments are *not* ignored""" xml = ("" "" "" "" "" "" "" "" "") (parser, interfaces, _) = _test_parser(xml) interface = interfaces.get('I.I') self.assertIsNotNone(interface) self.assertEquals(interface.comment, "Please consider me") meth = interface.methods.get('foo') self.assertIsNotNone(meth) self.assertEquals(meth.comment, "Notice me too") self.assertEquals(len(meth.arguments), 1) arg = meth.arguments[0] self.assertEquals(arg.comment, "And me!") def test_line_numbers(self): """Test that line numbers are correctly computed""" xml = ("\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n") (parser, interfaces, _) = _test_parser(xml) interface = interfaces.get('I.I') self.assertIsNotNone(interface) self.assertEqual(interface.line_number, 5) self.assertEqual(interface.comment_line_number, 2) meth = interface.methods.get('foo') self.assertIsNotNone(meth) self.assertEqual(meth.line_number, 9) self.assertEqual(meth.comment_line_number, 6) self.assertEquals(len(meth.arguments), 2) arg = meth.arguments[0] self.assertEqual(arg.line_number, 13) self.assertEqual(arg.comment_line_number, 10) arg = meth.arguments[1] self.assertEqual(arg.line_number, 14) self.assertEqual(arg.comment_line_number, -1) def test_doc_annotations(self): xml = ("" "" "" "") (parser, interfaces, _) = _test_parser(xml) interface = interfaces.get('I.I') self.assertIsNotNone(interface) self.assertEquals(interface.comment, "bla") def test_multiline_comments(self): xml = ("" "" "" "") (parser, interfaces, _) = _test_parser(xml) interface = interfaces.get('I.I') self.assertIsNotNone(interface) self.assertEquals(interface.comment, " Please consider that\n" " multiline comment") def test_ignored_comments(self): xml = ("" "" "" "" "" "") (parser, interfaces, _) = _test_parser(xml) interface = interfaces.get('I.I') self.assertIsNotNone(interface) self.assertIsNone(interface.comment) def test_root_node(self): self.assertParse("""""") def test_dbus_spec_example(self): """ Test parsing the example from the D-Bus Specification: http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format """ self.assertParse(""" """) class TestParserOutputCodes(unittest.TestCase): """Test the output codes from InterfaceParser.""" def test_unique(self): codes = InterfaceParser.get_output_codes() self.assertEqual(len(codes), len(set(codes))) def test_non_empty(self): codes = InterfaceParser.get_output_codes() self.assertNotEqual(codes, []) # pylint: disable=too-many-public-methods class TestParserRecovery(unittest.TestCase): """ Test recovery from parser errors in InterfaceParser to detect subsequent errors. """ # pylint: disable=invalid-name def assertOutput(self, xml, partial_output): # noqa (parser, interfaces, filename) = _test_parser(xml) self.assertEqual(interfaces, None) actual_output = \ [(filename, 'parser', i[0], i[1]) for i in partial_output] self.assertEqual(parser.get_output(), actual_output) return interfaces def test_annotation_interface(self): """Test recovery from invalid annotations in an interface.""" self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ('missing-attribute', 'Missing required attribute ‘name’ in method.'), ]) def test_annotation_method(self): """Test recovery from invalid annotations in a method.""" self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ('missing-attribute', 'Missing required attribute ‘type’ in arg.'), ]) def test_annotation_signal(self): """Test recovery from invalid annotations in a signal.""" self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ('missing-attribute', 'Missing required attribute ‘type’ in arg.'), ]) def test_annotation_property(self): """Test recovery from invalid annotations in a property.""" self.assertOutput( "" "" "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ('unknown-node', 'Unknown node ‘badnode’ in property ‘I.I.P’.'), ]) def test_annotation_arg(self): """Test recovery from invalid annotations in an arg.""" self.assertOutput( "" "" "", [ ('missing-attribute', 'Missing required attribute ‘name’ in annotation.'), ('unknown-node', 'Unknown node ‘badnode’ in argument ‘0 of method ‘I.I.M’’.'), ]) if __name__ == '__main__': # Run test suite unittest.main() dbus-deviation-0.6.0/dbusapi/tests/__init__.py0000664000175000017500000000000013026466535022132 0ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusapi/tests/test_ast.py0000775000175000017500000002225313035123314022223 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Unit tests for dbusapi.ast """ # pylint: disable=missing-docstring from dbusapi import ast from dbusapi.types import TypeSignature import unittest # pylint: disable=too-many-public-methods class TestAstNames(unittest.TestCase): """Test AST node name generation.""" def test_interface(self): iface = ast.Interface('Some.Interface') self.assertEqual(iface.format_name(), 'Some.Interface') def test_property(self): prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ) ast.Interface('Some.Interface', {}, { 'AProperty': prop, }) self.assertEqual(prop.format_name(), 'Some.Interface.AProperty') def test_property_unparented(self): prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ) self.assertEqual(prop.format_name(), 'AProperty') def test_method(self): method = ast.Method('AMethod', []) ast.Interface('Some.Interface', { 'AMethod': method, }) self.assertEqual(method.format_name(), 'Some.Interface.AMethod') def test_method_unparented(self): method = ast.Method('AMethod', []) self.assertEqual(method.format_name(), 'AMethod') def test_signal(self): signal = ast.Signal('SomeSignal', []) ast.Interface('Some.Interface', {}, {}, { 'SomeSignal': signal, }) self.assertEqual(signal.format_name(), 'Some.Interface.SomeSignal') def test_signal_unparented(self): signal = ast.Signal('SomeSignal', []) self.assertEqual(signal.format_name(), 'SomeSignal') def test_argument(self): arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's') ast.Method('ParentMethod', [arg]) self.assertEqual(arg.format_name(), '0 (‘self’) of method ‘ParentMethod’') def test_argument_unparented(self): arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's') self.assertEqual(arg.format_name(), '‘self’') def test_argument_unnamed(self): arg = ast.Argument(None, ast.Argument.DIRECTION_IN, 's') ast.Method('ParentMethod', [arg]) self.assertEqual(arg.format_name(), '0 of method ‘ParentMethod’') # pylint: disable=invalid-name def test_argument_unnamed_unparented(self): arg = ast.Argument(None, ast.Argument.DIRECTION_IN, 's') self.assertEqual(arg.format_name(), 'unnamed') def test_annotation_interface(self): annotation = ast.Annotation('SomeAnnotation', 'value') ast.Interface('Some.Interface', {}, {}, {}, { 'SomeAnnotation': annotation, }) self.assertEqual(annotation.format_name(), 'SomeAnnotation of ‘Some.Interface’') def test_annotation_property(self): annotation = ast.Annotation('SomeAnnotation', 'value') prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ, { 'SomeAnnotation': annotation, }) ast.Interface('Some.Interface', {}, { 'AProperty': prop, }) self.assertEqual(annotation.format_name(), 'SomeAnnotation of ‘Some.Interface.AProperty’') def test_annotation_method(self): annotation = ast.Annotation('SomeAnnotation', 'value') method = ast.Method('AMethod', [], { 'SomeAnnotation': annotation, }) ast.Interface('Some.Interface', { 'AMethod': method, }) self.assertEqual(annotation.format_name(), 'SomeAnnotation of ‘Some.Interface.AMethod’') def test_annotation_signal(self): annotation = ast.Annotation('SomeAnnotation', 'value') signal = ast.Signal('ASignal', [], { 'SomeAnnotation': annotation, }) ast.Interface('Some.Interface', {}, {}, { 'ASignal': signal, }) self.assertEqual(annotation.format_name(), 'SomeAnnotation of ‘Some.Interface.ASignal’') def test_annotation_argument(self): annotation = ast.Annotation('SomeAnnotation', 'value') arg = ast.Argument('Argument', ast.Argument.DIRECTION_IN, 's', { 'SomeAnnotation': annotation, }) method = ast.Method('AMethod', [arg]) ast.Interface('Some.Interface', { 'AMethod': method, }) self.assertEqual(annotation.format_name(), 'SomeAnnotation of ‘0 (‘Argument’)' ' of method ‘Some.Interface.AMethod’’') def test_annotation_unparented(self): annotation = ast.Annotation('SomeAnnotation', 'value') self.assertEqual(annotation.format_name(), 'SomeAnnotation') # pylint: disable=too-many-public-methods class TestAstParenting(unittest.TestCase): """Test AST node parenting.""" def test_property(self): annotation = ast.Annotation('SomeAnnotation', 'value') self.assertEqual(annotation.parent, None) prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ, { 'SomeAnnotation': annotation, }) self.assertEqual(prop.interface, None) self.assertEqual(annotation.parent, prop) iface = ast.Interface('Some.Interface', {}, { 'AProperty': prop, }) self.assertEqual(prop.interface, iface) def test_method(self): annotation = ast.Annotation('SomeAnnotation', 'value') self.assertEqual(annotation.parent, None) method = ast.Method('AMethod', [], { 'SomeAnnotation': annotation, }) self.assertEqual(method.interface, None) self.assertEqual(annotation.parent, method) iface = ast.Interface('Some.Interface', { 'AMethod': method, }) self.assertEqual(method.interface, iface) def test_signal(self): annotation = ast.Annotation('SomeAnnotation', 'value') self.assertEqual(annotation.parent, None) signal = ast.Signal('ASignal', [], { 'SomeAnnotation': annotation, }) self.assertEqual(signal.interface, None) self.assertEqual(annotation.parent, signal) iface = ast.Interface('Some.Interface', {}, { 'ASignal': signal, }) self.assertEqual(signal.interface, iface) def test_argument(self): annotation = ast.Annotation('SomeAnnotation', 'value') self.assertEqual(annotation.parent, None) arg = ast.Argument('SomeArgument', ast.Argument.DIRECTION_IN, 's', { 'SomeAnnotation': annotation, }) self.assertEqual(arg.parent, None) self.assertEqual(arg.index, -1) self.assertEqual(annotation.parent, arg) method = ast.Method('AMethod', [arg]) self.assertEqual(arg.parent, method) self.assertEqual(arg.index, 0) class TestAstTraversal(unittest.TestCase): """Test AST traversal.""" def test_walk(self): annotation = ast.Annotation('SomeAnnotation', 'value') method = ast.Method('AMethod', [], { 'SomeAnnotation': annotation, }) iface = ast.Interface('Some.Interface', { 'AMethod': method, }) children = [node for node in iface.walk()] self.assertEquals(len(children), 2) self.assertEquals(children[0], method) self.assertEquals(children[1], annotation) class TestAstSignatures(unittest.TestCase): """Test AST node signature parsing.""" def test_property(self): prop = ast.Property('AProperty', 's', ast.Property.ACCESS_READ) self.assertIsInstance(prop.type, TypeSignature) self.assertEqual(str(prop.type), 's') def test_argument(self): arg = ast.Argument('self', ast.Argument.DIRECTION_IN, 's') self.assertIsInstance(arg.type, TypeSignature) self.assertEqual(str(arg.type), 's') class TestAstLogging(unittest.TestCase): """Test error handling in AST""" def test_duplicate(self): method = ast.Method('AMethod', []) iface = ast.Interface('Some.Interface', { 'AMethod': method, }) self.assertListEqual(iface.log.issues, []) duplicate_method = ast.Method('AMethod', []) iface.add_child(duplicate_method) self.assertListEqual( iface.log.issues, [(None, 'ast', 'duplicate-method', 'Duplicate method definition ‘Some.Interface.AMethod’.')]) if __name__ == '__main__': # Run test suite unittest.main() dbus-deviation-0.6.0/HACKING0000664000175000017500000000154113026466535016232 0ustar philipphilip00000000000000Formatting ========== All code should follow PEP8. Commit messages =============== dbus-deviation does not use a ChangeLog; it is auto-generated from the git log when packaging a release. Commit messages should use a short tag in the commit summary to allow easy identification of which part of the code base they touch. Versioning ========== dbus-deviation uses an even–odd/stable–unstable versioning policy, where odd minor version numbers are unstable releases, released periodically (with increasing micro version numbers) and leading to a stable release with the next even minor version number. API breaks are allowed in micro releases with an odd minor version number, but not in micro releases with an even minor version number. It is encouraged to make a new micro release of an odd minor series after each large API or feature addition or break. dbus-deviation-0.6.0/dbus_deviation.egg-info/0000775000175000017500000000000013035747236021733 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbus_deviation.egg-info/requires.txt0000664000175000017500000000000513035747235024325 0ustar philipphilip00000000000000lxml dbus-deviation-0.6.0/dbus_deviation.egg-info/top_level.txt0000664000175000017500000000002613035747235024462 0ustar philipphilip00000000000000dbusapi dbusdeviation dbus-deviation-0.6.0/dbus_deviation.egg-info/PKG-INFO0000664000175000017500000001551513035747235023036 0ustar philipphilip00000000000000Metadata-Version: 1.0 Name: dbus-deviation Version: 0.6.0 Summary: Parse D-Bus introspection XML and process it in various ways Home-page: http://people.collabora.com/~pwith/dbus-deviation/ Author: Philip Withnall Author-email: philip.withnall@collabora.co.uk License: LGPLv2.1+ Description: dbus-deviation ============== dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation. A second Python module, dbusapi, is provided for parsing D-Bus introspection XML to produce an AST representing a D-Bus interface. dbus-deviation’s API is currently unstable and is likely to change wildly. Using dbus-deviation ==================== dbus-deviation can be used as a utility program or as a Python library. The utility programs: • dbus-interface-diff: Check for differences between two versions of the same D-Bus API and print details of each difference. It can check for problems with forwards and backwards compatibility, as well as general informational differences. Example: dbus-interface-diff \ com.example.Interface1.xml \ # old version of the interface com.example.Interface2.xml # new version of the interface • dbus-interface-vcs-helper: This is a helper program designed to be used by dbus-deviation.mk. As a library, the core object is an InterfaceParser, allowing a D-Bus API to be parsed and represented as an AST. See the API documentation for more explanation and examples. dbus-deviation.mk ----------------- This is a Makefile snippet which should be copied into your project, added to git, and the following two lines included in your top-level Makefile.am: dbus_api_xml_files = list of D-Bus interface XML files -include $(top_srcdir)/dbus-deviation.mk Do not add it to EXTRA_DIST. It is designed to work from git checkouts only. Then run: make dbus-deviation-mk-install to set up the API signature database. This assumes that your project defines D-Bus interfaces in XML files, and does not generate them at runtime. Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable. This script will automatically update the API signature database when a new release tag is pushed to the git remote. There is currently no streamlined support for projects which generate D-Bus interfaces at runtime. dbus-deviation.mk defines the following rules: • dist-dbus-api-compatibility (a dependency of dist-hook) • check-dbus-api-compatibility (a dependency of check-local) • dbus-deviation-mk-install (never triggered automatically) Dependencies ============ • argparse • lxml Development =========== For fun, dbus-deviation uses the following services to do continuous integration and gather build statistics: • https://travis-ci.org/pwithnall/dbus-deviation • https://landscape.io/github/pwithnall/dbus-deviation • https://coveralls.io/r/pwithnall/dbus-deviation • https://codecov.io/github/pwithnall/dbus-deviation Licensing ========= dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any later version). See COPYING for more details. dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the Academic Free License version 2.1, or the GNU General Public License version 2 (or, at your option, any later version). This is the same license as D-Bus itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license with less ambiguity about its implications. Bugs ==== Bug reports and patches should be sent via GitHub or Gitlab: https://github.com/pwithnall/dbus-deviation https://gitlab.com/dbus-deviation/dbus-deviation Contact ======= Philip Withnall http://people.collabora.com/~pwith/dbus-deviation/ Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0 ===================================================================== Major changes: • Split out the logging API to allow for reuse • Expose source line numbers in dbusapi.ast • Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov) • Drop Python 3.3 support as Sphinx no longer supports it • Add a D-Bus type formatter, dbusapi.typeformatter Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0 ===================================================================== Major changes: • Add support for elements (Kaloyan Tenchov) • Add object path, interface name and callable name validation (Kaloyan Tenchov) • Relicense to LGPLv2.1+ Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0 ===================================================================== Major changes: • Improve test coverage • Port to lxml instead of xml.etree • Support parsing comments and documentation nodes Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0 ===================================================================== Major changes: • Improve utility arguments and variables in dbus-deviation.mk and vcs-helper Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0 ===================================================================== Major changes: • Improve error reporting • Improve test coverage • Add Makefile snippet and helper program (vcs-helper) for use in other projects Initial release of dbus-deviation 0.1.0 ======================================= Major changes: • Initial version of the project Platform: UNKNOWN dbus-deviation-0.6.0/dbus_deviation.egg-info/zip-safe0000664000175000017500000000000113035744240023353 0ustar philipphilip00000000000000 dbus-deviation-0.6.0/dbus_deviation.egg-info/SOURCES.txt0000664000175000017500000000743613035747236023631 0ustar philipphilip00000000000000.gitignore .landscape.yaml .travis.yml AUTHORS COPYING HACKING NEWS README dbus-deviation.doap dbus-deviation.mk pre-push.hook setup.cfg setup.py version.py .be/version .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/settings .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/values .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/body .be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/values dbus_deviation.egg-info/PKG-INFO dbus_deviation.egg-info/SOURCES.txt dbus_deviation.egg-info/dependency_links.txt dbus_deviation.egg-info/entry_points.txt dbus_deviation.egg-info/requires.txt dbus_deviation.egg-info/top_level.txt dbus_deviation.egg-info/zip-safe dbusapi/__init__.py dbusapi/ast.py dbusapi/interfaceparser.py dbusapi/log.py dbusapi/typeformatter.py dbusapi/typeparser.py dbusapi/types.py dbusapi/tests/__init__.py dbusapi/tests/test_ast.py dbusapi/tests/test_interfaceparser.py dbusapi/tests/test_typeformatter.py dbusapi/tests/test_typeparser.py dbusdeviation/__init__.py dbusdeviation/interfacecomparator.py dbusdeviation/tests/__init__.py dbusdeviation/tests/test_interfacecomparator.py dbusdeviation/utilities/__init__.py dbusdeviation/utilities/diff.py dbusdeviation/utilities/vcs_helper.py docs/conf.py docs/dbusapi.rst docs/dbusdeviation.rst docs/dbusdeviation.utilities.rst docs/index.rst website/errors.html website/index.htmldbus-deviation-0.6.0/dbus_deviation.egg-info/dependency_links.txt0000664000175000017500000000000113035747235026000 0ustar philipphilip00000000000000 dbus-deviation-0.6.0/dbus_deviation.egg-info/entry_points.txt0000664000175000017500000000021713035747235025230 0ustar philipphilip00000000000000[console_scripts] dbus-interface-diff = dbusdeviation.utilities.diff:main dbus-interface-vcs-helper = dbusdeviation.utilities.vcs_helper:main dbus-deviation-0.6.0/docs/0000775000175000017500000000000013035747236016172 5ustar philipphilip00000000000000dbus-deviation-0.6.0/docs/dbusapi.rst0000664000175000017500000000155313035744274020356 0ustar philipphilip00000000000000dbusapi package =============== dbusapi.ast module ------------------ .. automodule:: dbusapi.ast :members: :undoc-members: :show-inheritance: dbusapi.interfaceparser module ------------------------------ .. automodule:: dbusapi.interfaceparser :members: :undoc-members: :show-inheritance: dbusapi.typeparser module ------------------------- .. automodule:: dbusapi.typeparser :members: :undoc-members: :show-inheritance: dbusapi.types module -------------------- .. automodule:: dbusapi.types :members: :undoc-members: :show-inheritance: dbusapi.typeformatter module ---------------------------- .. automodule:: dbusapi.typeformatter :members: :undoc-members: :show-inheritance: dbusapi.log module ------------------ .. automodule:: dbusapi.log :members: :undoc-members: :show-inheritance: dbus-deviation-0.6.0/docs/dbusdeviation.utilities.rst0000664000175000017500000000015513026466535023577 0ustar philipphilip00000000000000Command-line utilities ====================== dbus-interface-diff utility --------------------------- TODO dbus-deviation-0.6.0/docs/index.rst0000664000175000017500000000077013026466535020037 0ustar philipphilip00000000000000.. dbus-deviation documentation master file, created by sphinx-quickstart on Wed Apr 29 10:22:21 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to dbus-deviation’s documentation! ========================================== Contents: .. toctree:: :maxdepth: 4 dbusapi dbusdeviation dbusdeviation.utilities Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` dbus-deviation-0.6.0/docs/conf.py0000664000175000017500000002405313026466535017475 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # # dbus-deviation documentation build configuration file, created by # sphinx-quickstart on Wed Apr 29 10:22:21 2015. # # 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. import sys import os # 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. #sys.path.insert(0, os.path.abspath('.')) # -- 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.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'overridden in setup.py' author = u'Philip Withnall' copyright = u'2015, Philip Withnall' # 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 = 'overridden in setup.py' # The full version, including alpha/beta/rc tags. release = 'overridden in setup.py' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = 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 = 'default' # 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 themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- 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': '', } # 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 = [ ('index', project + '.tex', project + u' Documentation', author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', project, project + u' Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- 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 = [ ('index', project, project + u' Documentation', author, project, 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The basename for the epub file. It defaults to the project name. #epub_basename = project # The HTML theme for the epub output. Since the default themes are not optimized # for small screen space, using the same theme for HTML and epub output is # usually not wise. This defaults to 'epub', a theme designed to save visual # space. #epub_theme = 'epub' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. #epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True # Choose between 'default' and 'includehidden'. #epub_tocscope = 'default' # Fix unsupported image types using the PIL. #epub_fix_images = False # Scale large images. #epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. #epub_show_urls = 'inline' # If false, no index is generated. #epub_use_index = True dbus-deviation-0.6.0/docs/dbusdeviation.rst0000664000175000017500000000035313026466535021565 0ustar philipphilip00000000000000dbusdeviation package ===================== dbusdeviation.interfacecomparator module ---------------------------------------- .. automodule:: dbusdeviation.interfacecomparator :members: :undoc-members: :show-inheritance: dbus-deviation-0.6.0/PKG-INFO0000664000175000017500000001551513035747236016346 0ustar philipphilip00000000000000Metadata-Version: 1.0 Name: dbus-deviation Version: 0.6.0 Summary: Parse D-Bus introspection XML and process it in various ways Home-page: http://people.collabora.com/~pwith/dbus-deviation/ Author: Philip Withnall Author-email: philip.withnall@collabora.co.uk License: LGPLv2.1+ Description: dbus-deviation ============== dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation. A second Python module, dbusapi, is provided for parsing D-Bus introspection XML to produce an AST representing a D-Bus interface. dbus-deviation’s API is currently unstable and is likely to change wildly. Using dbus-deviation ==================== dbus-deviation can be used as a utility program or as a Python library. The utility programs: • dbus-interface-diff: Check for differences between two versions of the same D-Bus API and print details of each difference. It can check for problems with forwards and backwards compatibility, as well as general informational differences. Example: dbus-interface-diff \ com.example.Interface1.xml \ # old version of the interface com.example.Interface2.xml # new version of the interface • dbus-interface-vcs-helper: This is a helper program designed to be used by dbus-deviation.mk. As a library, the core object is an InterfaceParser, allowing a D-Bus API to be parsed and represented as an AST. See the API documentation for more explanation and examples. dbus-deviation.mk ----------------- This is a Makefile snippet which should be copied into your project, added to git, and the following two lines included in your top-level Makefile.am: dbus_api_xml_files = list of D-Bus interface XML files -include $(top_srcdir)/dbus-deviation.mk Do not add it to EXTRA_DIST. It is designed to work from git checkouts only. Then run: make dbus-deviation-mk-install to set up the API signature database. This assumes that your project defines D-Bus interfaces in XML files, and does not generate them at runtime. Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable. This script will automatically update the API signature database when a new release tag is pushed to the git remote. There is currently no streamlined support for projects which generate D-Bus interfaces at runtime. dbus-deviation.mk defines the following rules: • dist-dbus-api-compatibility (a dependency of dist-hook) • check-dbus-api-compatibility (a dependency of check-local) • dbus-deviation-mk-install (never triggered automatically) Dependencies ============ • argparse • lxml Development =========== For fun, dbus-deviation uses the following services to do continuous integration and gather build statistics: • https://travis-ci.org/pwithnall/dbus-deviation • https://landscape.io/github/pwithnall/dbus-deviation • https://coveralls.io/r/pwithnall/dbus-deviation • https://codecov.io/github/pwithnall/dbus-deviation Licensing ========= dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any later version). See COPYING for more details. dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the Academic Free License version 2.1, or the GNU General Public License version 2 (or, at your option, any later version). This is the same license as D-Bus itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license with less ambiguity about its implications. Bugs ==== Bug reports and patches should be sent via GitHub or Gitlab: https://github.com/pwithnall/dbus-deviation https://gitlab.com/dbus-deviation/dbus-deviation Contact ======= Philip Withnall http://people.collabora.com/~pwith/dbus-deviation/ Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0 ===================================================================== Major changes: • Split out the logging API to allow for reuse • Expose source line numbers in dbusapi.ast • Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov) • Drop Python 3.3 support as Sphinx no longer supports it • Add a D-Bus type formatter, dbusapi.typeformatter Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0 ===================================================================== Major changes: • Add support for elements (Kaloyan Tenchov) • Add object path, interface name and callable name validation (Kaloyan Tenchov) • Relicense to LGPLv2.1+ Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0 ===================================================================== Major changes: • Improve test coverage • Port to lxml instead of xml.etree • Support parsing comments and documentation nodes Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0 ===================================================================== Major changes: • Improve utility arguments and variables in dbus-deviation.mk and vcs-helper Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0 ===================================================================== Major changes: • Improve error reporting • Improve test coverage • Add Makefile snippet and helper program (vcs-helper) for use in other projects Initial release of dbus-deviation 0.1.0 ======================================= Major changes: • Initial version of the project Platform: UNKNOWN dbus-deviation-0.6.0/dbusdeviation/0000775000175000017500000000000013035747236020102 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusdeviation/interfacecomparator.py0000664000175000017500000005547713026466535024526 0ustar philipphilip00000000000000# -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Module providing a `InterfaceComparator` object for comparing two D-Bus APIs (provided as abstract syntax trees from the introspection XML), to determine if they differ in API-incompatible ways. """ from dbusapi import ast # Warning categories. WARNING_CATEGORIES = [ 'info', 'backwards-compatibility', 'forwards-compatibility', ] class InterfaceComparator(object): """ Compare two D-Bus interface descriptions and determine how they differ. Differences are given different severity levels, depending on whether they affect * nothing, and are purely decorative; for example, changing the name of a method argument * forwards compatibility, where code written against the new interface may not work against the old interface; for example, because it uses a newly added method * backwards compatibility, where code written against the old interface may not work against the new interface; for example, because it changes the type of a property """ # Output severity levels. OUTPUT_INFO = 'info' OUTPUT_FORWARDS_INCOMPATIBLE = 'forwards-compatibility' OUTPUT_BACKWARDS_INCOMPATIBLE = 'backwards-compatibility' def __init__(self, old_interfaces, new_interfaces, enabled_warnings=None, disabled_warnings=None, new_filename=None): """ Construct a new InterfaceComparator. Args: old_interfaces: non-empty dict of old interfaces, mapping interface name to an ast.Interface instance new_interfaces: non-empty dict of new interfaces, mapping interface name to an ast.Interface instance enabled_warnings: potentially empty list of warning categories and codes to enable disabled_warnings: potentially empty list of warning categories and codes to disable new_filename: path to the new D-Bus interface file, or None if unknown """ self._old_interfaces = old_interfaces self._new_interfaces = new_interfaces self._new_filename = new_filename self._output = [] if enabled_warnings is not None: self._enabled_warnings = enabled_warnings else: self._enabled_warnings = WARNING_CATEGORIES if disabled_warnings is not None: self._disabled_warnings = disabled_warnings else: self._disabled_warnings = [] @staticmethod def get_output_codes(): """Return a list of all possible output codes.""" # FIXME: Hard-coded for the moment. return [ 'interface-added', 'interface-removed', 'deprecated', 'undeprecated' 'c-symbol-changed', 'reply-added', 'reply-removed', 'ecs-changed-true-invalidates', 'ecs-changed-true-false', 'ecs-changed-true-const', 'ecs-changed-invalidates-true', 'ecs-changed-invalidates-false', 'ecs-changed-invalidates-const', 'ecs-changed-false-invalidates', 'ecs-changed-false-true', 'ecs-changed-false-const', 'ecs-changed-const-invalidates', 'ecs-changed-const-true', 'ecs-changed-const-false', 'method-added', 'method-removed', 'property-added', 'property-removed', 'signal-added', 'signal-removed', 'argument-added', 'argument-removed', 'property-type-changed', 'property-access-changed-read-readwrite', 'property-access-changed-read-write', 'property-access-changed-write-read', 'property-access-changed-write-readwrite', 'property-access-changed-readwrite-read', 'property-access-changed-readwrite-write', 'argument-name-changed', 'argument-type-changed', 'argument-direction-changed-in-out', 'argument-direction-changed-out-in', ] def _issue_output(self, level, code, message): """Append a message to the comparator output.""" self._output.append((self._new_filename, level, code, message)) def _warning_enabled(self, level, code): """Determine whether the given output level is enabled for output.""" return ((level in self._enabled_warnings and level not in self._disabled_warnings and code not in self._disabled_warnings) or (code in self._enabled_warnings and code not in self._disabled_warnings)) def get_output(self): """ Return all the log messages generated by the latest call to compare(). Disabled warnings will not be returned. """ out = [] for (filename, level, code, message) in self._output: if not self._warning_enabled(level, code): continue out.append((filename, level, code, message)) return out def compare(self): """ Compare the two interfaces and store the results. Returns: The list of relevant warnings to output; an empty list otherwise. The return value is affected by the categories of enabled warnings. """ self._output = [] for (name, interface) in self._old_interfaces.items(): # See if the old interface exists in the new file. if name not in self._new_interfaces: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'interface-removed', 'Interface ‘%s’ has been removed.' % name) else: # Compare the two. self._compare_interfaces(interface, self._new_interfaces[name]) for (name, interface) in self._new_interfaces.items(): # See if the new interface exists in the old file. if name not in self._old_interfaces: self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, 'interface-added', 'Interface ‘%s’ has been added.' % name) # Work out the exit status. return self.get_output() # pylint: disable=too-many-branches def _compare_annotations(self, old_node, new_node): # noqa """Compare two ast.Annotation instances.""" def _get_string_annotation(node, annotation_name, default): """ Get an annotation value as a string. Reference: http://goo.gl/3EtdNf Returns: The value of the `annotation_name` annotation as a string, or `default` if no annotation exists by that name. """ if annotation_name in node.annotations: return node.annotations[annotation_name].value return default def _get_bool_annotation(node, annotation_name, default): """ Get an annotation value as a boolean. Reference: http://goo.gl/3EtdNf Returns: The value of the `annotation_name` annotation as a boolean, or `default` if no annotation exists by that name. """ if annotation_name in node.annotations: return node.annotations[annotation_name].value == 'true' return default def _get_ecs_annotation(node): """ Get the value of the EmitsChangedSignal annotation. Reference: http://goo.gl/3EtdNf Returns: The value of the `org.freedesktop.DBus.Property.EmitsChangedSignal` annotation, if it exists, or the default, calculated as per the specification. """ name = 'org.freedesktop.DBus.Property.EmitsChangedSignal' if name in node.annotations: return node.annotations[name].value elif isinstance(node, ast.Property): assert node.interface is not None return _get_ecs_annotation(node.interface) else: return 'true' old_deprecated = \ _get_bool_annotation(old_node, 'org.freedesktop.DBus.Deprecated', False) new_deprecated = \ _get_bool_annotation(new_node, 'org.freedesktop.DBus.Deprecated', False) if old_deprecated and not new_deprecated: self._issue_output(self.OUTPUT_INFO, 'undeprecated', 'Node ‘%s’ has been un-deprecated.' % old_node.format_name()) elif not old_deprecated and new_deprecated: self._issue_output(self.OUTPUT_INFO, 'deprecated', 'Node ‘%s’ has been deprecated.' % old_node.format_name()) old_c_symbol = \ _get_string_annotation(old_node, 'org.freedesktop.DBus.GLib.CSymbol', '') new_c_symbol = \ _get_string_annotation(new_node, 'org.freedesktop.DBus.GLib.CSymbol', '') if old_c_symbol != new_c_symbol: self._issue_output(self.OUTPUT_INFO, 'c-symbol-changed', 'Node ‘%s’ has changed its C symbol from ‘%s’ ' 'to ‘%s’.' % (old_node.format_name(), old_c_symbol, new_c_symbol)) old_no_reply = \ _get_bool_annotation(old_node, 'org.freedesktop.DBus.Method.NoReply', False) new_no_reply = \ _get_bool_annotation(new_node, 'org.freedesktop.DBus.Method.NoReply', False) if old_no_reply and not new_no_reply: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'reply-added', 'Node ‘%s’ has been marked as returning a ' 'reply.' % old_node.format_name()) elif not old_no_reply and new_no_reply: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'reply-removed', 'Node ‘%s’ has been marked as not returning a ' 'reply.' % old_node.format_name()) old_ecs = _get_ecs_annotation(old_node) new_ecs = _get_ecs_annotation(new_node) output_code = 'ecs-changed-%s-%s' % (old_ecs, new_ecs) # New # | true | invalidates | const | false # | true | xxxx | B2 | F3 | F3 # Old | invalidates | B2 | xxxxxxxxxxx | F3 | F3 # | const | B1 | B1 | xxxxx | B4 # | false | B1 | B1 | F4 | xxxxx # # B = Backwards-compatible; F = Forwards-compatible # 1 = Started notifying # 2 = Property switched lists in PropertiesChanged # 3 = Stopped notifying # 4 = const semantics changed if old_ecs in ['true', 'invalidates'] and \ new_ecs in ['false', 'const']: self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ stopped emitting ' 'org.freedesktop.DBus.Properties.' 'PropertiesChanged.' % old_node.format_name()) elif (old_ecs in ['false', 'const'] and new_ecs in ['true', 'invalidates']): self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ started emitting ' 'org.freedesktop.DBus.Properties.' 'PropertiesChanged.' % old_node.format_name()) elif old_ecs == 'true' and new_ecs == 'invalidates': self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ stopped emitting its new value in ' 'org.freedesktop.DBus.Properties.' 'PropertiesChanged.' % old_node.format_name()) elif old_ecs == 'invalidates' and new_ecs == 'true': self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ started emitting its new value in ' 'org.freedesktop.DBus.Properties.' 'PropertiesChanged.' % old_node.format_name()) elif old_ecs == 'const' and new_ecs == 'false': self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ stopped being a constant.' % old_node.format_name()) elif old_ecs == 'false' and new_ecs == 'const': self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, output_code, 'Node ‘%s’ became a constant.' % old_node.format_name()) # pylint: disable=too-many-branches def _compare_interfaces(self, old_interface, new_interface): # noqa """Compare two ast.Interface instances.""" # Precondition of calling this method. assert old_interface.name == new_interface.name # Compare methods. for (name, method) in old_interface.methods.items(): if name not in new_interface.methods: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'method-removed', 'Method ‘%s’ has been removed.' % method.format_name()) else: self._compare_methods(method, new_interface.methods[name]) for (name, method) in new_interface.methods.items(): if name not in old_interface.methods: self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, 'method-added', 'Method ‘%s’ has been added.' % method.format_name()) # Compare properties for (name, prop) in old_interface.properties.items(): if name not in new_interface.properties: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-removed', 'Property ‘%s’ has been removed.' % prop.format_name()) else: self._compare_properties(prop, new_interface.properties[name]) for (name, prop) in new_interface.properties.items(): if name not in old_interface.properties: self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, 'property-added', 'Property ‘%s’ has been added.' % prop.format_name()) # Compare signals for (name, signal) in old_interface.signals.items(): if name not in new_interface.signals: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'signal-removed', 'Signal ‘%s’ has been removed.' % signal.format_name()) else: self._compare_signals(signal, new_interface.signals[name]) for (name, signal) in new_interface.signals.items(): if name not in old_interface.signals: self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, 'signal-added', 'Signal ‘%s’ has been added.' % signal.format_name()) # Compare annotations self._compare_annotations(old_interface, new_interface) def _compare_methods(self, old_method, new_method): """Compare two ast.Method instances.""" # Precondition of calling this method. assert old_method.name == new_method.name # Compare the argument lists. n_old_args = len(old_method.arguments) n_new_args = len(new_method.arguments) for i in range(max(n_old_args, n_new_args)): if i >= n_old_args: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-added', 'Argument %s ' 'has been added.' % new_method.arguments[i].format_name()) elif i >= n_new_args: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-removed', 'Argument %s ' 'has been removed.' % old_method.arguments[i].format_name()) else: self._compare_arguments(old_method.arguments[i], new_method.arguments[i]) # Compare annotations self._compare_annotations(old_method, new_method) def _compare_properties(self, old_property, new_property): """Compare two ast.Property instances.""" # Precondition of calling this method. assert old_property.name == new_property.name if old_property.type != new_property.type: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-type-changed', 'Property ‘%s’ has changed type from ‘%s’ ' 'to ‘%s’.' % (old_property.format_name(), old_property.type, new_property.type)) error_code = 'property-access-changed-%s-%s' % \ (old_property.access, new_property.access) if (old_property.access == ast.Property.ACCESS_READ or old_property.access == ast.Property.ACCESS_WRITE) and \ new_property.access == ast.Property.ACCESS_READWRITE: # Property has become less restrictive. self._issue_output(self.OUTPUT_FORWARDS_INCOMPATIBLE, error_code, 'Property ‘%s’ has changed access from ' '‘%s’ to ‘%s’, becoming less restrictive.' % (old_property.format_name(), old_property.access, new_property.access)) elif old_property.access != new_property.access: # Access has changed incompatibly. self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, error_code, 'Property ‘%s’ has changed access from ' '‘%s’ to ‘%s’.' % (old_property.format_name(), old_property.access, new_property.access)) # Compare annotations self._compare_annotations(old_property, new_property) def _compare_signals(self, old_signal, new_signal): """Compare two ast.Signal instances.""" # Precondition of calling this method. assert old_signal.name == new_signal.name # Compare the argument lists. n_old_args = len(old_signal.arguments) n_new_args = len(new_signal.arguments) for i in range(max(n_old_args, n_new_args)): if i >= n_old_args: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-added', 'Argument %s ' 'has been added.' % new_signal.arguments[i].format_name()) elif i >= n_new_args: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-removed', 'Argument %s ' 'has been removed.' % old_signal.arguments[i].format_name()) else: self._compare_arguments(old_signal.arguments[i], new_signal.arguments[i]) # Compare annotations self._compare_annotations(old_signal, new_signal) def _compare_arguments(self, old_arg, new_arg): """Compare two ast.Argument instances.""" if old_arg.name != new_arg.name: self._issue_output(self.OUTPUT_INFO, 'argument-name-changed', 'Argument %s has changed ' 'name from ‘%s’ to ‘%s’.' % (old_arg.pretty_name, old_arg.name, new_arg.name)) if old_arg.type != new_arg.type: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-type-changed', 'Argument %s has changed ' 'type from ‘%s’ to ‘%s’.' % (old_arg.pretty_name, old_arg.type, new_arg.type)) if old_arg.direction != new_arg.direction: self._issue_output(self.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-direction-changed-%s-%s' % (old_arg.direction, new_arg.direction), 'Argument %s has changed ' 'direction from ‘%s’ to ‘%s’.' % (old_arg.pretty_name, old_arg.direction, new_arg.direction)) # Compare annotations self._compare_annotations(old_arg, new_arg) dbus-deviation-0.6.0/dbusdeviation/utilities/0000775000175000017500000000000013035747236022115 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusdeviation/utilities/diff.py0000775000175000017500000001767013026466535023415 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015, 2016 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ D-Bus interface comparator. This allows two D-Bus introspection XML files to be compared for API compatibility, and any incompatibilities debugged. Compatibility warnings are split into categories, separating forwards- and backwards-compatibility in case one or other is not cared about by the interface maintainer. See the documentation for InterfaceComparator for an explanation of the two types of compatibility. """ import argparse import os import sys from lxml import etree from dbusapi.interfaceparser import InterfaceParser from dbusdeviation.interfacecomparator import InterfaceComparator # Warning categories. WARNING_CATEGORIES = [ 'info', 'backwards-compatibility', 'forwards-compatibility', 'parser', ] def _parse_file(filename, parser): """ Parse an XML interface file. Return a list of ast.Interfaces. If the file is empty, return an empty list. Print an error and exit if parsing fails. """ try: interfaces = parser.parse() # Handle parse errors. if interfaces is None: _print_output(parser.get_output()) sys.exit(1) return interfaces # pylint: disable=E1101 except etree.XMLSyntaxError as err: # If the file is empty, treat it as a non-existent Interface. This # allows for diffs of added files. if os.path.getsize(filename) == 0: return {} else: sys.stderr.write('%s: error: %s\n' % (filename, err)) sys.exit(1) def _format_level(level, enable_colour=True, justified_length=0): """Format a warning level as a human-readable string.""" colours = { 'info': '\033[96;1m', 'forwards-compatibility': '\033[35;1m', 'backwards-compatibility': '\033[91;1m', 'parser': '\033[91;1m', } formats = { 'info': 'note', 'forwards-compatibility': 'warn', 'backwards-compatibility': 'error', 'parser': 'error', } out = formats[level] if justified_length > 0: out = out.rjust(justified_length) if enable_colour: out = colours[level] + out + '\033[0m' return out def _get_fd_for_level(level): """Get the output file descriptor to use for the output level.""" if level == InterfaceComparator.OUTPUT_INFO: return sys.stdout return sys.stderr def _print_output(output, include_uris=True, enable_colour=True): """ Print all the log messages generated by the latest call to compare(). The messages will be printed to stdout and/or stderr as appropriate. """ # Justify the error codes. try: max_code_len = max([len(o[2]) for o in output]) max_level_len = max([len(_format_level(o[1], False)) for o in output]) except ValueError: max_code_len = 0 max_level_len = 0 for (filename, level, code, message) in output: formatted_level = _format_level(level, enable_colour, max_level_len) fd_for_level = _get_fd_for_level(level) errors_page = 'https://people.collabora.com' \ '/~pwith/dbus-deviation/errors.html' explanation_uri = '%s#%s' % (errors_page, code) formatted_code = code.rjust(max_code_len) if enable_colour: formatted_code = '\033[1m%s\033[0m' % formatted_code explanation_uri = '\033[90m%s\033[0m' % explanation_uri else: formatted_code = formatted_code if filename is None: output = '%s: %s: %s\n' % \ (formatted_level, formatted_code, message) else: output = '%s: %s: %s: %s\n' % \ (filename, formatted_level, formatted_code, message) fd_for_level.write(output) if include_uris: fd_for_level.write(' %s\n' % explanation_uri) def _calculate_exit_status(args, output): """ Build the exit status for the program. This will be non-zero if any errors were outputted, or if --fatal-warnings was passed and any warnings were outputted. Otherwise it will be zero. """ outputted_errors = False outputted_warnings = False for (_, level, _, _) in output: if level in ['backwards-compatibility', 'parser']: outputted_errors = True elif level in ['forwards-compatibility']: outputted_warnings = True if outputted_errors or (args.fatal_warnings and outputted_warnings): return len(output) return 0 def main(): """Main utility implementation.""" codes = InterfaceComparator.get_output_codes() codes += InterfaceParser.get_output_codes() # Parse command line arguments. parser = argparse.ArgumentParser( description='Comparing D-Bus interface definitions') parser.add_argument('old_file', type=str, help='Old interface XML file') parser.add_argument('new_file', type=str, help='New interface XML file') parser.add_argument('--file-display-name', dest='file_display_name', type=str, help='Human-readable name for new interface XML file ' '(if --new-file is a pipe, for example)') parser.add_argument('--fatal-warnings', action='store_const', const=True, default=False, help='Treat all warnings as fatal') parser.add_argument('--warnings', dest='warnings', metavar='CATEGORY,…', type=str, help='Warning categories (%s; %s)' % (', '.join(WARNING_CATEGORIES), ', '.join(codes))) args = parser.parse_args() if not args.old_file or not args.new_file: parser.print_help() sys.exit(1) if args.warnings is None or args.warnings == 'all': # Enable all warnings by default warnings_args = WARNING_CATEGORIES elif args.warnings == 'none': warnings_args = [] else: warnings_args = args.warnings.split(',') for warning_arg in warnings_args: if warning_arg[:3] == 'no-': warning_arg = warning_arg[3:] if warning_arg not in WARNING_CATEGORIES and warning_arg not in codes: sys.stderr.write('%s: Unrecognized warning ‘%s’.\n' % (sys.argv[0], warning_arg)) parser.print_help() sys.exit(1) enabled_warnings = [arg for arg in warnings_args if arg[:3] != 'no-'] disabled_warnings = [arg[3:] for arg in warnings_args if arg[:3] == 'no-'] # Parse the two files. old_parser = InterfaceParser(args.old_file) new_parser = InterfaceParser(args.new_file) old_interfaces = _parse_file(args.old_file, old_parser) new_interfaces = _parse_file(args.new_file, new_parser) # Work out the human-readable name of the new XML filename. if args.file_display_name is not None: new_filename = args.file_display_name else: new_filename = args.new_file # Compare the interfaces. comparator = InterfaceComparator(old_interfaces, new_interfaces, enabled_warnings, disabled_warnings, new_filename) out = comparator.compare() _print_output(out) sys.exit(_calculate_exit_status(args, out)) if __name__ == '__main__': main() dbus-deviation-0.6.0/dbusdeviation/utilities/__init__.py0000664000175000017500000000000013026466535024214 0ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusdeviation/utilities/vcs_helper.py0000775000175000017500000004562413026466535024637 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Wrapper around dbus-interface-diff to integrate it with a VCS This implements an API signature database in the project’s version control system so that all users of the VCS can do API compatibility between all past signed releases. Currently, only git is supported. Requirements: • Support out-of-tree builds, where srcdir ≠ builddir. • Support bare repositories, or where GIT_WORK_TREE ≠ .git. • Exit with status 0 if GIT_DIR does not exist; support the use case of running `make distcheck` inside an extracted release tarball. • Support both tag-before-dist and tag-after-dist workflows. """ import argparse from contextlib import contextmanager import os import pipes import shlex import shutil import subprocess import sys import tempfile @contextmanager def named_pipe(): """Create and cleanup a named pipe in a temporary directory.""" dirname = tempfile.mkdtemp() try: path = os.path.join(dirname, 'fifo') os.mkfifo(path) yield path finally: shutil.rmtree(dirname) def _git_command(args, command): """Build a git command line with standard arguments.""" out = [args.git] if args.git_dir != '': out += ['--git-dir', args.git_dir] if args.git_work_tree != '': out += ['--work-tree', args.git_work_tree] return out + [command] def _format_command(args): """Local wrapper for pipes.quote() with support for Python 2.7.""" try: # pylint: disable=no-member return ' '.join(shlex.quote(a) for a in args) except AttributeError: try: # pylint: disable=no-member return ' '.join(pipes.quote(a) for a in args) except AttributeError: # Give up return ' '.join(args) def _get_contents_of_file(args, tag, api_xml_file): """Get the git object ID of api_xml_file in the tag revision.""" rev = subprocess.check_output(_git_command(args, 'rev-parse') + ['--verify', '--quiet', '%s^{tag}:%s' % (tag, api_xml_file)]) return rev.strip().decode('utf-8') def _set_notes_for_ref(args, tag, api_xml_basename, notes): """Store the notes object ID as api_xml_basename in the tag revision.""" with open(os.devnull, 'w') as dev_null: subprocess.check_output(_git_command(args, 'notes') + ['--ref', 'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename), 'add', '-C', notes, tag], stderr=dev_null) def _notes_exist_for_ref(args, tag, api_xml_basename): """Check whether notes have already been stored for this file and tag.""" with open(os.devnull, 'w') as dev_null: status = subprocess.call(_git_command(args, 'notes') + ['--ref', 'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename), 'show', tag], stdout=dev_null, stderr=dev_null) return status == 0 def _get_notes_filename_for_head(args, api_xml_basename): """Get the filename of api_xml_basename in the git work tree.""" filename = subprocess.check_output(_git_command(args, 'ls-files') + ['--full-name', '*/%s' % api_xml_basename]) filename = filename.strip().decode('utf-8') # Resolve the relative path against the work tree. if args.git_work_tree != '': filename = os.path.join(args.git_work_tree, filename) return filename def _fetch_notes(args): """Fetch the latest API signature database from the remote.""" subprocess.check_output(_git_command(args, 'fetch') + [args.git_remote_origin, 'refs/%s/*:refs/%s/*' % (args.dbus_api_git_refs, args.dbus_api_git_refs)]) def _push_notes(args): """Push the local API signature database to the remote.""" command = _git_command(args, 'push') command += [args.git_remote_origin, 'refs/' + args.dbus_api_git_refs + '/*'] if not args.no_push: subprocess.check_output(command) else: sys.stdout.write('Run this command to push the API signature ' 'database:\n' ' %s\n' % _format_command(command)) def _is_release(args, ref): """Check whether ref identifies a signed tag.""" with open(os.devnull, 'w') as dev_null: code = subprocess.call(_git_command(args, 'rev-parse') + ['--verify', ref], stdout=dev_null, stderr=dev_null) return code == 0 def _get_latest_release(args): """Get the name of the latest signed tag.""" tag_list = subprocess.check_output(_git_command(args, 'rev-list') + ['--tags', '--max-count=1']) tag_list = tag_list.strip().decode('utf-8').split('\n') latest_tag = subprocess.check_output(_git_command(args, 'describe') + ['--tags'] + tag_list) return latest_tag.strip().decode('utf-8') def command_dist(args): """Store the current API signature against the latest signed tag.""" # Get the latest git tag try: latest_tag = _get_latest_release(args) except subprocess.CalledProcessError: sys.stderr.write('error: Failed to find latest git tag: %s.') return 1 # Store notes for each API file for api_xml_file in args.dbus_api_xml_files: try: api_xml_basename = os.path.basename(api_xml_file) # Do notes already exist for this file and tag? if args.ignore_existing and \ _notes_exist_for_ref(args, latest_tag, api_xml_basename): sys.stdout.write('%s: Ignored XML file ‘%s’; already has ' 'a note\n' % (latest_tag, api_xml_basename)) continue notes = _get_contents_of_file(args, latest_tag, api_xml_file) subprocess.check_output(_git_command(args, 'notes') + ['--ref', 'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename), 'add', '-C', notes, latest_tag]) sys.stdout.write('%s: Added note ‘%s’ for XML file ‘%s’\n' % (latest_tag, notes, api_xml_basename)) except subprocess.CalledProcessError: sys.stderr.write('error: Failed to store notes for API file ' '‘%s’ and git tag ‘%s’.\n' % (api_xml_file, latest_tag)) return 1 # Push to the remote try: _push_notes(args) except subprocess.CalledProcessError: sys.stderr.write('error: Failed to push notes to remote ‘%s’.\n' % args.git_remote_origin) return 1 return 0 # pylint: disable=too-many-locals,too-many-branches,too-many-statements def command_check(args): """ Check for API differences between two tags. If old_ref is not specified, it defaults to the latest signed tag. If new_ref is not specified, it defaults to the git work tree. """ if args.old_ref != '' and not _is_release(args, args.old_ref): sys.stderr.write('error: Invalid --old-ref ‘%s’\n' % args.old_ref) return 1 if args.new_ref != '' and not _is_release(args, args.new_ref): sys.stderr.write('error: Invalid --new-ref ‘%s’\n' % args.new_ref) return 1 try: _fetch_notes(args) except subprocess.CalledProcessError: # Continue anyway sys.stderr.write('error: Failed to fetch latest refs.\n') old_ref = args.old_ref new_ref = args.new_ref if old_ref == '': # Get the latest git tag try: old_ref = _get_latest_release(args) except subprocess.CalledProcessError: sys.stderr.write('error: Failed to find latest git tag.\n') return 1 try: refs = subprocess.check_output(_git_command(args, 'for-each-ref') + ['--format=%(refname)', 'refs/%s' % args.dbus_api_git_refs]) refs = refs.strip().decode('utf-8').split('\n') except subprocess.CalledProcessError: sys.stderr.write('error: Failed to get ref list.\n') return 1 retval = 0 for note_ref in refs: api_xml_basename = os.path.basename(note_ref) if args.silent: sys.stdout.write(' DIFF %s\n' % api_xml_basename) else: sys.stdout.write('Comparing %s\n' % api_xml_basename) with named_pipe() as old_pipe_path, named_pipe() as new_pipe_path: old_notes_filename = old_pipe_path if new_ref == '': new_notes_filename = \ _get_notes_filename_for_head(args, api_xml_basename) else: new_notes_filename = new_pipe_path diff_command = ['dbus-interface-diff', '--warnings', args.warnings, '--file-display-name', api_xml_basename] if args.fatal_warnings: diff_command += ['--fatal-warnings'] diff_command += [old_notes_filename, new_notes_filename] old_notes_command = _git_command(args, 'notes') + [ '--ref', 'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename), 'show', old_ref, ] new_notes_command = _git_command(args, 'notes') + [ '--ref', 'refs/%s/%s' % (args.dbus_api_git_refs, api_xml_basename), 'show', new_ref, ] diff_proc = subprocess.Popen(diff_command) with open(old_pipe_path, 'wb') as old_pipe, \ open(os.devnull, 'w') as dev_null: old_notes_proc = subprocess.Popen(old_notes_command, stdout=old_pipe, stderr=dev_null) if new_ref == '': new_notes_proc = None # Debug output. Roughly equivalent to `set -v`. if not args.silent: ls_files_command = _git_command(args, 'ls-files') + [ '--full-name', '*/%s' % api_xml_basename, ] if args.git_work_tree != '': git_work_tree = args.git_work_tree + '/' else: git_work_tree = '' sys.stdout.write('%s \\\n' ' <(%s) \\\n' ' %s`%s`\n' % (_format_command(diff_command[:-2]), _format_command(old_notes_command), git_work_tree, _format_command(ls_files_command))) else: with open(new_pipe_path, 'wb') as new_pipe: new_notes_proc = subprocess.Popen(new_notes_command, stdout=new_pipe, stderr=dev_null) # Debug output. Roughly equivalent to `set -v`. if not args.silent: sys.stdout.write('%s \\\n' ' <(%s) \\\n' ' <(%s)\n' % (_format_command(diff_command[:-2]), _format_command(old_notes_command), _format_command(new_notes_command))) old_notes_proc.communicate() old_notes_proc.wait() if new_notes_proc is not None: new_notes_proc.wait() diff_proc.wait() # Output the status from the first failure if retval == 0 and diff_proc.returncode != 0: retval = diff_proc.returncode return retval def command_install(args): """Set up the API signature database for all existing signed tags.""" try: tag_list = subprocess.check_output(_git_command(args, 'tag')) tag_list = tag_list.strip().decode('utf-8').split('\n') except subprocess.CalledProcessError: sys.stderr.write('error: Failed to get tag list.\n') return 1 for tag in tag_list: outputted = False for api_xml_file in args.dbus_api_xml_files: api_xml_basename = os.path.basename(api_xml_file) try: notes = _get_contents_of_file(args, tag, api_xml_file) except subprocess.CalledProcessError: # Ignore it. notes = '' if notes == '': continue try: _set_notes_for_ref(args, tag, api_xml_basename, notes) sys.stdout.write('%s: Added note ‘%s’ for XML file ‘%s’\n' % (tag, notes, api_xml_basename)) outputted = True except subprocess.CalledProcessError: # Ignore it continue if not outputted: sys.stdout.write('%s: Nothing to do\n' % tag) # Push the new refs try: _push_notes(args) except subprocess.CalledProcessError: sys.stderr.write('error: Failed to push notes to remote ‘%s’.\n' % args.git_remote_origin) return 1 return 0 def main(): """Main helper implementation.""" # Parse command line arguments. parser = argparse.ArgumentParser( description='Comparing D-Bus interface definitions') # Common arguments parser.add_argument('--silent', action='store_const', const=True, default=False, help='Silence all non-error output') # pylint: disable=bad-continuation parser.add_argument('--git', type=str, default='git', metavar='COMMAND', help='Path to the git command, including extra ' 'arguments') parser.add_argument('--git-dir', type=str, default='', metavar='PATH', help='Path to the git directory in the project ' 'checkout') parser.add_argument('--git-work-tree', type=str, default='', metavar='PATH', help='Path to the git work tree for the project') parser.add_argument('--git-remote', dest='git_remote_origin', type=str, default='origin', metavar='REMOTE', help='git remote to push notes to') # pylint: disable=bad-continuation parser.add_argument('--git-refs', dest='dbus_api_git_refs', type=str, default='notes/dbus/api', metavar='REF-PATH', help='Path beneath refs/ where the git notes will be' ' stored containing the API signatures database') parser.add_argument('--no-push', action='store_const', const=True, default=False, help='Disable automatic pushing the API signature ' 'database to a remote repository') subparsers = parser.add_subparsers() # dist command parser_dist = subparsers.add_parser('dist') parser_dist.add_argument('dbus_api_xml_files', metavar='API-FILE', type=str, nargs='+', help='D-Bus API XML file to check') parser_dist.add_argument('--ignore-existing', action='store_const', const=True, default=False, help='Ignore existing API signatures rather than ' 'erroring') parser_dist.set_defaults(func=command_dist) # check command parser_check = subparsers.add_parser('check') parser_check.add_argument('--diff-warnings', dest='warnings', type=str, default='all', help='Comma-separated list of warnings to ' 'enable when running dbus-interface-diff') parser_check.add_argument('--fatal-warnings', action='store_const', const=True, default=False, help='Treat all warnings as fatal') # pylint: disable=bad-continuation parser_check.add_argument('old_ref', metavar='OLD-REF', type=str, nargs='?', default='', help='Old ref to compare; or empty for the ' 'latest signed tag') parser_check.add_argument('new_ref', metavar='NEW-REF', type=str, nargs='?', default='', help='New ref to compare; or empty for HEAD') parser_check.set_defaults(func=command_check) # install command parser_install = subparsers.add_parser('install') parser_install.add_argument('dbus_api_xml_files', metavar='API-FILE', type=str, nargs='+', help='D-Bus API XML file to install') parser_install.set_defaults(func=command_install) args = parser.parse_args() # Bail early if the .git directory does not exist, since that's our data # store. git_dir = args.git_dir if git_dir == '': git_dir = os.path.join(args.git_work_tree, '.git') if not os.path.isdir(git_dir): sys.stderr.write('error: Could not find git directory ‘%s’. ' 'Skipping.\n' % git_dir) return 0 return args.func(args) if __name__ == '__main__': main() dbus-deviation-0.6.0/dbusdeviation/__init__.py0000664000175000017500000000000013026466535022201 0ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusdeviation/tests/0000775000175000017500000000000013035747236021244 5ustar philipphilip00000000000000dbus-deviation-0.6.0/dbusdeviation/tests/test_interfacecomparator.py0000775000175000017500000006201013026466535026707 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Unit tests for dbus-interface-diff """ # pylint: disable=missing-docstring from dbusapi.interfaceparser import InterfaceParser from dbusdeviation.interfacecomparator import InterfaceComparator import os import tempfile import unittest def _create_temp_xml_file(xml): """Create a temporary XML file with the given contents.""" tmp_fd, tmp_name = tempfile.mkstemp(suffix='.xml', text=True) xml_fd = os.fdopen(tmp_fd, 'wt') xml_fd.write(xml) xml_fd.close() return tmp_name # pylint: disable=too-many-public-methods class TestComparatorErrors(unittest.TestCase): """Test log output from InterfaceComparator.""" def _test_comparator(self, old_xml, new_xml): """Build an InterfaceComparator for the two parsed XML snippets.""" old_tmpfile = _create_temp_xml_file(old_xml) new_tmpfile = _create_temp_xml_file(new_xml) old_parser = InterfaceParser(old_tmpfile) new_parser = InterfaceParser(new_tmpfile) old_interfaces = old_parser.parse() new_interfaces = new_parser.parse() os.unlink(new_tmpfile) os.unlink(old_tmpfile) self.assertEqual(old_parser.get_output(), []) self.assertEqual(new_parser.get_output(), []) self.assertNotEqual(old_interfaces, None) self.assertNotEqual(new_interfaces, None) return InterfaceComparator(old_interfaces, new_interfaces) # pylint: disable=invalid-name def assertOutput(self, old_xml, new_xml, output): # noqa comparator = self._test_comparator(old_xml, new_xml) self.assertEqual(comparator.compare(), output) self.assertEqual(comparator.get_output(), output) def test_interface_removed(self): self.assertOutput( "", "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'interface-removed', 'Interface ‘I.A’ has been removed.'), ]) def test_interface_added(self): self.assertOutput( "", "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'interface-added', 'Interface ‘I.A’ has been added.'), ]) def test_interface_deprecated(self): self.assertOutput( "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'deprecated', 'Node ‘I.A’ has been deprecated.'), ]) def test_interface_deprecated_explicit(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'deprecated', 'Node ‘I.A’ has been deprecated.'), ]) def test_interface_undeprecated(self): self.assertOutput( "" "" "", "", [ (None, InterfaceComparator.OUTPUT_INFO, 'undeprecated', 'Node ‘I.A’ has been un-deprecated.'), ]) def test_interface_undeprecated_explicit(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'undeprecated', 'Node ‘I.A’ has been un-deprecated.'), ]) def test_interface_name_changed(self): self.assertOutput( "", "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'interface-removed', 'Interface ‘I.A’ has been removed.'), (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'interface-added', 'Interface ‘I.A2’ has been added.') ]) def test_method_removed(self): self.assertOutput( "" "", "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'method-removed', 'Method ‘I.A.M’ has been removed.'), ]) def test_method_added(self): self.assertOutput( "", "" "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'method-added', 'Method ‘I.A.M’ has been added.'), ]) def test_method_name_changed(self): self.assertOutput( "" "", "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'method-removed', 'Method ‘I.A.M’ has been removed.'), (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'method-added', 'Method ‘I.A.M2’ has been added.'), ]) def test_method_arg_removed(self): self.assertOutput( "" "" "", "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-removed', 'Argument 0 of method ‘I.A.M’ has been removed.'), ]) def test_method_arg_added(self): self.assertOutput( "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-added', 'Argument 0 of method ‘I.A.M’ has been added.'), ]) def test_method_arg_name_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'argument-name-changed', 'Argument 0 (‘A’) of method ‘I.A.M’ has changed name ' 'from ‘A’ to ‘Z’.'), ]) def test_method_arg_type_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-type-changed', 'Argument 0 of method ‘I.A.M’ has changed type from ‘s’ to ' '‘b’.'), ]) def test_method_arg_direction_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-direction-changed-in-out', 'Argument 0 of method ‘I.A.M’ has changed direction ' 'from ‘in’ to ‘out’.'), ]) def test_method_c_symbol_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'c-symbol-changed', 'Node ‘I.A.M’ has changed its C symbol from ‘S1’ to ‘S2’.'), ]) def test_method_no_reply_changed_to_false(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'reply-added', 'Node ‘I.A.M’ has been marked as returning a reply.'), ]) def test_method_no_reply_changed_to_true(self): self.assertOutput( "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'reply-removed', 'Node ‘I.A.M’ has been marked as not returning a reply.'), ]) def test_method_no_reply_changed_to_true_explicit(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'reply-removed', 'Node ‘I.A.M’ has been marked as not returning a reply.'), ]) def test_property_removed(self): self.assertOutput( "" "" "", "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-removed', 'Property ‘I.A.P’ has been removed.'), ]) def test_property_added(self): self.assertOutput( "", "" "" "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'property-added', 'Property ‘I.A.P’ has been added.'), ]) def test_property_name_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-removed', 'Property ‘I.A.P’ has been removed.'), (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'property-added', 'Property ‘I.A.P2’ has been added.'), ]) def test_property_type_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-type-changed', 'Property ‘I.A.P’ has changed type from ‘b’ to ‘s’.'), ]) def test_property_access_changed_r_to_rw(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'property-access-changed-read-readwrite', 'Property ‘I.A.P’ has changed access from ‘read’ to ' '‘readwrite’, becoming less restrictive.'), ]) def test_property_access_changed_w_to_rw(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'property-access-changed-write-readwrite', 'Property ‘I.A.P’ has changed access from ‘write’ to ' '‘readwrite’, becoming less restrictive.'), ]) def test_property_access_changed_r_to_w(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-access-changed-read-write', 'Property ‘I.A.P’ has changed access ' 'from ‘read’ to ‘write’.'), ]) def test_property_access_changed_rw_to_r(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-access-changed-readwrite-read', 'Property ‘I.A.P’ has changed access from ‘readwrite’ to ' '‘read’.'), ]) def test_property_access_changed_rw_to_w(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'property-access-changed-readwrite-write', 'Property ‘I.A.P’ has changed access from ‘readwrite’ to ' '‘write’.'), ]) def test_property_emits_changed_signal_changed(self): # All the classes of error we expect. error1 = ( InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ started emitting ' 'org.freedesktop.DBus.Properties.PropertiesChanged.' ) error2a = ( InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ stopped emitting its new value in ' 'org.freedesktop.DBus.Properties.PropertiesChanged.' ) error2b = ( InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ started emitting its new value in ' 'org.freedesktop.DBus.Properties.PropertiesChanged.' ) error3 = ( InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ stopped emitting ' 'org.freedesktop.DBus.Properties.PropertiesChanged.' ) error4a = ( InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ stopped being a constant.' ) error4b = ( InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'Node ‘I.A.P’ became a constant.' ) # 2D matrix of test vectors. Each row gives the old annotation value; # each column the new annotation value. Rows and columns are labelled # by the corresponding index in labels. e.g. We expect error2a when # changing annotation value from 'true' to 'invalidates'. labels = ['true', 'invalidates', 'const', 'false'] vectors = [ [None, error2a, error3, error3], [error2b, None, error3, error3], [error1, error1, None, error4a], [error1, error1, error4b, None], ] for i in range(0, len(vectors)): for j in range(0, len(vectors[0])): vector = vectors[i][j] if vector is not None: vector = ( None, vector[0], 'ecs-changed-%s-%s' % (labels[i], labels[j]), vector[1], ) expected_errors = [vector] if vector is not None else [] self.assertOutput( "" "" "" "" "" % labels[i], "" "" "" "" "" % labels[j], expected_errors) def test_signal_removed(self): self.assertOutput( "" "", "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'signal-removed', 'Signal ‘I.A.S’ has been removed.'), ]) def test_signal_added(self): self.assertOutput( "", "" "", [ (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'signal-added', 'Signal ‘I.A.S’ has been added.'), ]) def test_signal_name_changed(self): self.assertOutput( "" "", "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'signal-removed', 'Signal ‘I.A.S’ has been removed.'), (None, InterfaceComparator.OUTPUT_FORWARDS_INCOMPATIBLE, 'signal-added', 'Signal ‘I.A.S2’ has been added.'), ]) def test_signal_arg_removed(self): self.assertOutput( "" "" "", "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-removed', 'Argument 0 of signal ‘I.A.S’ has been removed.'), ]) def test_signal_arg_added(self): self.assertOutput( "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-added', 'Argument 0 of signal ‘I.A.S’ has been added.'), ]) def test_signal_arg_name_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_INFO, 'argument-name-changed', 'Argument 0 (‘A’) of signal ‘I.A.S’ has changed name ' 'from ‘A’ to ‘Z’.'), ]) def test_signal_arg_type_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-type-changed', 'Argument 0 of signal ‘I.A.S’ has changed type from ‘s’ to ' '‘b’.'), ]) def test_signal_arg_direction_changed(self): self.assertOutput( "" "" "", "" "" "", [ (None, InterfaceComparator.OUTPUT_BACKWARDS_INCOMPATIBLE, 'argument-direction-changed-in-out', 'Argument 0 of signal ‘I.A.S’ has changed direction ' 'from ‘in’ to ‘out’.'), ]) if __name__ == '__main__': # Run test suite unittest.main() dbus-deviation-0.6.0/dbusdeviation/tests/__init__.py0000664000175000017500000000000013026466535023343 0ustar philipphilip00000000000000dbus-deviation-0.6.0/COPYING0000664000175000017500000006347213026466535016311 0ustar philipphilip00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! dbus-deviation-0.6.0/AUTHORS0000664000175000017500000000006213026466535016310 0ustar philipphilip00000000000000Philip Withnall dbus-deviation-0.6.0/README0000664000175000017500000000721013026466535016122 0ustar philipphilip00000000000000dbus-deviation ============== dbus-deviation is a project for parsing D-Bus introspection XML and processing it in various ways. Its main tool is dbus-interface-diff, which calculates the difference between two D-Bus APIs for the purpose of checking for API breaks. This functionality is also available as a Python module, dbusdeviation. A second Python module, dbusapi, is provided for parsing D-Bus introspection XML to produce an AST representing a D-Bus interface. dbus-deviation’s API is currently unstable and is likely to change wildly. Using dbus-deviation ==================== dbus-deviation can be used as a utility program or as a Python library. The utility programs: • dbus-interface-diff: Check for differences between two versions of the same D-Bus API and print details of each difference. It can check for problems with forwards and backwards compatibility, as well as general informational differences. Example: dbus-interface-diff \ com.example.Interface1.xml \ # old version of the interface com.example.Interface2.xml # new version of the interface • dbus-interface-vcs-helper: This is a helper program designed to be used by dbus-deviation.mk. As a library, the core object is an InterfaceParser, allowing a D-Bus API to be parsed and represented as an AST. See the API documentation for more explanation and examples. dbus-deviation.mk ----------------- This is a Makefile snippet which should be copied into your project, added to git, and the following two lines included in your top-level Makefile.am: dbus_api_xml_files = list of D-Bus interface XML files -include $(top_srcdir)/dbus-deviation.mk Do not add it to EXTRA_DIST. It is designed to work from git checkouts only. Then run: make dbus-deviation-mk-install to set up the API signature database. This assumes that your project defines D-Bus interfaces in XML files, and does not generate them at runtime. Finally, copy pre-push.hook to .git/hooks/pre-push and ensure it’s executable. This script will automatically update the API signature database when a new release tag is pushed to the git remote. There is currently no streamlined support for projects which generate D-Bus interfaces at runtime. dbus-deviation.mk defines the following rules: • dist-dbus-api-compatibility (a dependency of dist-hook) • check-dbus-api-compatibility (a dependency of check-local) • dbus-deviation-mk-install (never triggered automatically) Dependencies ============ • argparse • lxml Development =========== For fun, dbus-deviation uses the following services to do continuous integration and gather build statistics: • https://travis-ci.org/pwithnall/dbus-deviation • https://landscape.io/github/pwithnall/dbus-deviation • https://coveralls.io/r/pwithnall/dbus-deviation • https://codecov.io/github/pwithnall/dbus-deviation Licensing ========= dbus-deviation is licensed under the LGPL version 2.1 (or, at your option, any later version). See COPYING for more details. dbus-deviation versions 0.4.0 and earlier were licensed under the choice of the Academic Free License version 2.1, or the GNU General Public License version 2 (or, at your option, any later version). This is the same license as D-Bus itself. Version 0.5.0 was relicensed to LGPLv2.1+ as it’s a more standard license with less ambiguity about its implications. Bugs ==== Bug reports and patches should be sent via GitHub or Gitlab: https://github.com/pwithnall/dbus-deviation https://gitlab.com/dbus-deviation/dbus-deviation Contact ======= Philip Withnall http://people.collabora.com/~pwith/dbus-deviation/ dbus-deviation-0.6.0/NEWS0000664000175000017500000000327213035745151015737 0ustar philipphilip00000000000000Overview of changes from dbus-deviation 0.5.0 to dbus-deviation 0.6.0 ===================================================================== Major changes: • Split out the logging API to allow for reuse • Expose source line numbers in dbusapi.ast • Add a D-Bus type parser, dbusapi.typeparser (thanks to Kaloyan Tenchov) • Drop Python 3.3 support as Sphinx no longer supports it • Add a D-Bus type formatter, dbusapi.typeformatter Overview of changes from dbus-deviation 0.4.0 to dbus-deviation 0.5.0 ===================================================================== Major changes: • Add support for elements (Kaloyan Tenchov) • Add object path, interface name and callable name validation (Kaloyan Tenchov) • Relicense to LGPLv2.1+ Overview of changes from dbus-deviation 0.3.0 to dbus-deviation 0.4.0 ===================================================================== Major changes: • Improve test coverage • Port to lxml instead of xml.etree • Support parsing comments and documentation nodes Overview of changes from dbus-deviation 0.2.0 to dbus-deviation 0.3.0 ===================================================================== Major changes: • Improve utility arguments and variables in dbus-deviation.mk and vcs-helper Overview of changes from dbus-deviation 0.1.0 to dbus-deviation 0.2.0 ===================================================================== Major changes: • Improve error reporting • Improve test coverage • Add Makefile snippet and helper program (vcs-helper) for use in other projects Initial release of dbus-deviation 0.1.0 ======================================= Major changes: • Initial version of the project dbus-deviation-0.6.0/.landscape.yaml0000664000175000017500000000027413026466535020141 0ustar philipphilip00000000000000doc-warnings: true test-warnings: false strictness: veryhigh pep8: full: true pep257: disable: - D211 - D400 ignore-paths: - docs/conf.py - setup.py - version.py dbus-deviation-0.6.0/setup.py0000775000175000017500000000656313026500444016755 0ustar philipphilip00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright © 2015 Collabora Ltd. # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2.1 of the License, or (at your option) # any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . """ Parse D-Bus introspection XML and process it in various ways """ from setuptools import setup, find_packages import os import version # https://gist.github.com/pwithnall/7bc5f320b3bdf418265a project_name = 'dbus-deviation' __version__ = version.get_version() project_author = 'Philip Withnall' README = open('README').read() NEWS = open('NEWS').read() # From http://stackoverflow.com/a/17004263/2931197 def discover_and_run_tests(): import os import sys import unittest # get setup.py directory setup_file = sys.modules['__main__'].__file__ setup_dir = os.path.abspath(os.path.dirname(setup_file)) # use the default shared TestLoader instance test_loader = unittest.defaultTestLoader # use the basic test runner that outputs to sys.stderr test_runner = unittest.TextTestRunner() # automatically discover all tests # NOTE: only works for python 2.7 and later test_suite = test_loader.discover(setup_dir) # run the test suite test_runner.run(test_suite) try: from setuptools.command.test import test class DiscoverTest(test): def finalize_options(self): test.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): discover_and_run_tests() except ImportError: from distutils.core import Command class DiscoverTest(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): discover_and_run_tests() setup( name=project_name, version=__version__, packages=find_packages(exclude=['*.tests']), include_package_data=True, exclude_package_data={'': ['.gitignore']}, zip_safe=True, setup_requires=[ 'setuptools_git >= 0.3', 'setuptools_pep8', 'sphinx', ], install_requires=['lxml'], tests_require=[], entry_points={ 'console_scripts': [ 'dbus-interface-diff = dbusdeviation.utilities.diff:main', 'dbus-interface-vcs-helper = ' 'dbusdeviation.utilities.vcs_helper:main', ], }, author=project_author, author_email='philip.withnall@collabora.co.uk', description=__doc__, long_description=README + '\n\n' + NEWS, license='LGPLv2.1+', url='http://people.collabora.com/~pwith/dbus-deviation/', cmdclass={'test': DiscoverTest}, command_options={ 'build_sphinx': { 'project': ('setup.py', project_name), 'version': ('setup.py', __version__), 'release': ('setup.py', __version__), }, }, ) dbus-deviation-0.6.0/.travis.yml0000664000175000017500000000145213035123314017336 0ustar philipphilip00000000000000language: python python: - "2.7" - "3.4" - "3.5" # Disabled due to lxml 3.5.0 not working with PyPy: # See: https://bitbucket.org/pypy/compatibility/wiki/lxml # - "pypy" # Disabled due to bugs in the logilab package on the Python 3.2 build slave. # See: https://travis-ci.org/pwithnall/dbus-deviation/jobs/60500460 # - "3.2" # command to install dependencies install: - pip install pylint coverage coveralls codecov . # command to run tests script: - coverage run --source dbusapi,dbusdeviation --omit 'dbusdeviation/utilities/*' setup.py test - coverage report --fail-under=70 - python setup.py check - python setup.py pep8 - pylint --errors-only dbusapi - pylint --errors-only dbusdeviation # Submit coverage data to coveralls.io and codecov.io after_success: - coveralls - codecov dbus-deviation-0.6.0/.be/0000775000175000017500000000000013035747236015706 5ustar philipphilip00000000000000dbus-deviation-0.6.0/.be/version0000664000175000017500000000003713026466535017316 0ustar philipphilip00000000000000Bugs Everywhere Directory v1.5 dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/0000775000175000017500000000000013035747236023146 5ustar philipphilip00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0000775000175000017500000000000013035747236024106 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775000175000017500000000000013035747236030340 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775000175000017500000000000013035747236030340 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000775000175000017500000000000013035747236030340 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664000175000017500000000104513026466535030342 0ustar philipphilip00000000000000Support projects which generate their D-Bus introspection data at runtime, and define their D-Bus APIs using C structs, rather than static XML files. This requires building and running the project, then calling the D-Bus Introspect() method on it to get the API signature to put in the database.; Build a tool which stores given generated files, then lists the remaining tags which don't have API signatures stored for them; doing something stateful like `git rebase -i` would be too complex to integrate with a build process for a first attempt. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/comments/2cedc3e0-2161-4efa-8440-0c7a79eafe41/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664000175000017500000000026113026466535030341 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Thu, 14 May 2015 16:37:50 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714cf3b/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/ff2af64b-7e23-4ab0-a249-09a31714c0000664000175000017500000000052713026466535030346 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "open", "summary": "Add a helper for dynamically generated APIs", "time": "Thu, 14 May 2015 16:36:29 +0000" } ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775000175000017500000000000013035747236030512 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775000175000017500000000000013035747236030512 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000775000175000017500000000000013035747236030512 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664000175000017500000000027313026466535030516 0ustar philipphilip00000000000000Improve the output formatting so it’s nicer to read. Give each log message an ID which can be used to refer to documentation, and which can be used to individually disable the message. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/comments/44b1317e-bff8-477e-9d13-50071beb825b/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664000175000017500000000026113026466535030513 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Wed, 29 Apr 2015 14:29:39 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda70a/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/f044aaea-7072-47b9-88d2-56aec1dda0000664000175000017500000000050613026466535030515 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "fixed", "summary": "Improve output formatting", "time": "Wed, 29 Apr 2015 14:28:54 +0000" } ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775000175000017500000000000013035747236030342 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775000175000017500000000000013035747236030342 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000775000175000017500000000000013035747236030342 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664000175000017500000000052613026466535030347 0ustar philipphilip00000000000000Allow different D-Bus interfaces to have different stability policies, so that one interface could be stable while another is unstable, and both be checked by dbus-interface-diff. In fact, it might make sense to add an attribute to the D-Bus introspection XML files which states the stability of each interface in a machine readable manner. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/comments/11c90632-88a0-4999-bfa4-19fd5857291e/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664000175000017500000000026113026466535030343 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Mon, 01 Jun 2015 16:29:58 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6bbb0/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/5dfaf020-f32b-4202-8546-5ec16fb6b0000664000175000017500000000053713026466535030351 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "open", "summary": "Allow stability policy to differ between interfaces", "time": "Mon, 01 Jun 2015 16:28:48 +0000" } ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775000175000017500000000000013035747236030320 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775000175000017500000000000013035747236030320 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775000175000017500000000000013035747236030320 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664000175000017500000000007613026466535030325 0ustar philipphilip00000000000000Fixed with revision 720a0d94c01626d6f086feb47793d863d8bb5573. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/0b90dd22-dcf6-43e7-88a2-b72bdcef61c9/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664000175000017500000000026113026466535030321 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Thu, 07 May 2015 18:31:31 +0000" } ././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000775000175000017500000000000013035747236030320 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664000175000017500000000033113026466535030317 0ustar philipphilip00000000000000Add autotools macros to support the git integration workflow suggested here: https://bugs.freedesktop.org/show_bug.cgi?id=89845#c2 Document the whole thing really well and port a few modules to it as an example. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/comments/43ad8034-30c8-42f3-9c0d-61f13d9b8537/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664000175000017500000000026113026466535030321 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Wed, 29 Apr 2015 13:46:43 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb4642/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/0e712e0e-6b80-4884-8cfe-96248ffb40000664000175000017500000000052713026466535030326 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "fixed", "summary": "Add autotools integration for git workflow", "time": "Wed, 29 Apr 2015 13:45:38 +0000" } ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775000175000017500000000000013035747236030445 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775000175000017500000000000013035747236030445 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000775000175000017500000000000013035747236030445 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664000175000017500000000032513026466535030447 0ustar philipphilip00000000000000Add a helper for use with `git bisect` to work out which commit broke D-Bus API compatibility. This is mostly useful for repositories which define their D-Bus APIs in code, rather than as introspection XML files. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/comments/4581dbb2-105c-42f8-8858-7b8a4071a058/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664000175000017500000000026113026466535030446 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Fri, 29 Apr 2016 16:36:26 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1669e3/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/6ed5c660-1af9-4737-92bd-acae4f1660000664000175000017500000000050213026466535030444 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "open", "summary": "Add git bisect support", "time": "Fri, 29 Apr 2016 16:35:31 +0000" } ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775000175000017500000000000013035747236030732 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775000175000017500000000000013035747236030732 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775000175000017500000000000013035747236030732 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664000175000017500000000024213026466535030732 0ustar philipphilip00000000000000Add dbus-deviation to http://coveralls.io once it’s back online. See also: https://github.com/audreyr/how-to/blob/master/python/use_coverage_with_unittest.rst ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/91b7a01a-4b27-4617-900f-fd1243852d04/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664000175000017500000000026113026466535030733 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Wed, 29 Apr 2015 13:52:42 +0000" } ././@LongLink0000000000000000000000000000022700000000000011216 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000775000175000017500000000000013035747236030732 5ustar philipphilip00000000000000././@LongLink0000000000000000000000000000023300000000000011213 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/bodydbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664000175000017500000000007413026466535030735 0ustar philipphilip00000000000000Fixed with commit 423e1b1adac21d8a9d4d97fd15d6099654c259d0. ././@LongLink0000000000000000000000000000023500000000000011215 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/comments/1ae388e0-0d45-4faf-8744-471c338140c3/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664000175000017500000000026113026466535030733 0ustar philipphilip00000000000000{ "Author": "Philip Withnall ", "Content-type": "text/plain", "Date": "Thu, 14 May 2015 16:36:03 +0000" } ././@LongLink0000000000000000000000000000015700000000000011220 Lustar 00000000000000dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce688be/valuesdbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/bugs/1e1ffc0d-b1e0-4dba-b17d-d9f66ce680000664000175000017500000000050013026466535030727 0ustar philipphilip00000000000000{ "creator": "Philip Withnall ", "reporter": "Philip Withnall ", "severity": "minor", "status": "fixed", "summary": "Add to coveralls.io", "time": "Wed, 29 Apr 2015 13:52:18 +0000" } dbus-deviation-0.6.0/.be/95ae9c1f-cbd6-48d4-bb7c-0fdb75e9eb62/settings0000664000175000017500000000000313026466535024722 0ustar philipphilip00000000000000{} dbus-deviation-0.6.0/version.py0000664000175000017500000000372713026466535017312 0ustar philipphilip00000000000000# This program is placed into the public domain. """ Gets the current version number. If in a git repository, it is the current git tag. Otherwise it is the one contained in the PKG-INFO file. To use this script, simply import it in your setup.py file and use the results of get_version() as your package version: from version import * setup( ... version=get_version(), ... ) """ __all__ = ('get_version') from os.path import dirname, isdir, join import os import re import subprocess version_re = re.compile('^Version: (.+)$', re.M) def get_version(): d = dirname(__file__) if isdir(join(d, '.git')): # Get the version using "git describe". cmd = 'git describe --tags --match [0-9]*'.split() try: version = subprocess.check_output(cmd).decode().strip() except subprocess.CalledProcessError: print('Unable to get version number from git tags') exit(1) # PEP 386 compatibility if '-' in version: version = '.post'.join(version.split('-')[:2]) # Don't declare a version "dirty" merely because a time stamp has # changed. If it is dirty, append a ".dev1" suffix to indicate a # development revision after the release. with open(os.devnull, 'w') as fd_devnull: subprocess.call(['git', 'status'], stdout=fd_devnull, stderr=fd_devnull) cmd = 'git diff-index --name-only HEAD'.split() try: dirty = subprocess.check_output(cmd).decode().strip() except subprocess.CalledProcessError: print('Unable to get git index status') exit(1) if dirty != '': version += '.dev1' else: # Extract the version from the PKG-INFO file. with open(join(d, 'PKG-INFO')) as f: version = version_re.search(f.read()).group(1) return version if __name__ == '__main__': print(get_version())