Plugins can serve various kinds of purposes, such as the following:
Regardless of the purpose, the main steps of creating a plugin are the same:
In this section, we will review these steps briefly. In the following sections, we will show how to apply these steps in the context of example plugins that extend Bazaar in different ways.
We have prepared a few simple plugins to use as examples for extending Bazaar's functionality in different ways:
summary
: This plugin adds a new command to print a brief summary of a branch and its filescustomlog
: This plugin adds custom log formats, extending the functionality of the bzr log
commandappendlog
: This plugin contains a hook that can be used to automatically append commit logs to a file configured in a branchAlthough these plugins are very simplistic, you might find them useful as templates when implementing a plugin. As all the examples in the book, these plugins are distributed under Creative Commons license; feel free to use them in any way.
While reading this section, it may be helpful to look at the implementation of these plugins and understand what they do. They should also serve as easy-to-follow use cases of bzrlib
components.
The installation procedure is the same for all these plugins. Simply branch from Launchpad into your personal plugins directory, as follows:
$ mkdir -p ~/.bazaar/plugins $ bzr branch lp:~bzrbook/bzrbook-examples/bzr-summary ~/.bazaar/plugins/summary $ bzr branch lp:~bzrbook/bzrbook-examples/bzr-customlog ~/.bazaar/plugins/customlog $ bzr branch lp:~bzrbook/bzrbook-examples/bzr-appendlog ~/.bazaar/plugins/appendlog
Verify that the plugins are correctly installed by running the bzr plugins
command. In the output, you should see these plugins with no error messages.
This plugin adds a summary command, which prints a brief summary of a branch and its files. For example:
$ bzr summary -r15 --group-by-ext /sandbox/plugins/summary # Branch URL: file:///sandbox/plugins/summary/ # Branch nick: summary # Revisions: 23 # Selected revno: 15 # Files: 5 # ---: 1 # py : 4 # Directories: 1 # Others: 0
In addition to the basic information we can get with bzr info
, the command counts the number of files and directories, optionally grouped by the file type. The -r
option can be used to select a revision; it is implemented by re-using Bazaar's built-in revision selector you have seen in all bzr
commands. As with all bzr
commands, the -h
and --help
flags can be used to print detailed help with all the options of the command.
This plugin adds custom log formats that can be used with the bzr log
command. For example:
$ bzr log /sandbox/plugins/summary/ --git --limit 1 commit janos@axiom-20130114172512-vkaw1yzw1aodr8a7 Bazaar revno: 23 Author: Janos Gyerik <janos@axiom> Date: Mon Jan 14 18:25:12 2013 +0100 changed "pivot revno" to "selected revno" in output
The --git
flag is not a standard parameter of the bzr log
command; it is added by the plugin. The output mimics the format used by Git. Bazaar's log formats hide merged revisions by default. In order to mimic Git's behavior, this plugin will always show merged revisions.
The plugin is written in a way to make it easy to add other custom log formats. After reading this section about creating a plugin, it should be straightforward to duplicate an existing log format to create a new one.
The appendlog
plugin contains a hook that can be used to automatically append commit logs to a file configured in a branch. To enable the logging, the path to the log file must be specified in the post_config_log
variable in the configuration file .bzr/branch/branch.conf
inside a branch. Let's create a dummy branch to test this:
$ bzr init /tmp/dummy Created a standalone tree (format: 2a)
And let's enable the hook in the branch configuration file by using the bzr config
command. For example:
$ cd /tmp/dummy $ bzr config post_commit_log=/tmp/changes.log
If you commit a couple of revisions in this dummy branch, they will be logged in the file /tmp/changes.log
, using the long log format.
Bazaar plugins must be valid Python packages; therefore, you must name the directory of a plugin accordingly, otherwise it cannot be imported. You should also follow common naming conventions of Python packages explained in the PEP8 document at http://www.python.org/dev/peps/pep-0008/#package-and-module-names.
In particular, Python package names should be short, all lowercase names, and the use of underscores is discouraged.
During development, it is easiest to create the plugin inside your personal plugins area:
$HOME/.bazaar/plugins/
%APPDATA%azaar2.0plugins
Alternatively, you can set the BZR_PLUGIN_PATH
environment variable to a directory that contains plugins. For example, if you are developing a plugin in /sandbox/bzr-plugins/customlog
, then you should set the following:
BZR_PLUGIN_PATH=/sandbox/bzr-plugins
Bazaar discovers plugins installed in the following order of precedence:
BZR_PLUGIN_PATH
BZRLIB/plugins
The following filesystem layout is strongly recommended when implementing a plugin:
README
: This file contains a general explanation of what the plugin does, how to install it, and how to use it__init__.py
: This file contains the initialization code, meta information such as an appropriate docstring describing the plugin and version number, and a test suite definition, the Python files, possibly organized in subpackages, implementing the main functionality of the plugintests/*
: This file contains the implementation of the test suitesetup.py
: This file contains the installer scriptIt makes sense to implement the files in the preceding order, and test the functionality you are adding gradually. The documentation specifies certain conventions and writing styles to use in the implementation, in order for the plugin to integrate well into Bazaar's architecture. It is good to follow the guidelines and best practices, especially if you intend to share the plugin with others.
Essentially, this is just a text file and not used by Bazaar itself, but you should always include a well-written README
file. The file is typically named README
without a .txt
extension. A common practice is to use the markdown format, which is essentially an easy-to-read, easy-to-write plaintext format. For example:
This is a simple plugin to define custom log formats. Installation ------------ The simplest way to install it for a single user is with: bzr branch lp:~bzrbook/bzrbook-examples/bzr-customlog ~/.bazaar/plugins/customlog
For more, real-world examples, see the README
files in the plugins included in your installation, inside the BZRLIB/plugins
directory.
The syntax of the markdown format is documented at http://daringfireball.net/projects/markdown/syntax.
A plugin must be a valid Python package, therefore an __init__.py
file must exist in the plugin's directory. The file should contain important meta information about the plugin that will be used in the various help
commands and for determining API compatibility with the installed version of Bazaar, namely the following:
The first statement in the file
must be a string literal, commonly named the docstring in Python. The first line of docstring is used as the description of the plugin when listing plugins with bzr plugins. The full docstring is used when viewing the detailed help of the plugin with bzr help plugins/NAME
. For example, the docstring in the example customlog
plugin is as follows:
"""Custom log formats to use with ``bzr log --CUSTOMNAME`` TODO: more explanation..."""
The first line of this text will appear in the output of bzr plugins:
$ bzr plugins | grep customlog -A1 customlog 1.0.0dev Custom log formats to use with ``bzr log --CUSTOMNAME``
The full docstring text will appear in bzr help plugins/customlog
:
$ bzr help plugins/customlog Custom log formats to use with ``bzr log --CUSTOMNAME`` TODO: more explanation...
The plugin should declare the
bzrlib
API version it depends on, as follows:
import bzrlib from bzrlib.api import require_api require_api(bzrlib, (2, 5, 0))
Bazaar will load the plugin only if the bzrlib
API version is equal to or higher than the specified version. If a plugin cannot be loaded because it requires a newer API than the current Bazaar installation, the bzr
plugins
command will show that as an error. For example:
$ bzr plugins | grep customlog -A1 customlog (failed to load) ** Unable to load plugin u'customlog'. It requested API version (3, 5, 0) of module <module 'bzrlib' from '/Library/Python/2.6/site-packages/bzrlib/__init__.pyc'> but the minimum exported version is (2, 4, 0), and the maximum is (2, 5, 0)
In this case, the plugin will not be loaded, and the bzr help plugins/NAME
command will not work either.
The right value to use for these settings is the lowest API version with which the plugin was confirmed to work well. The version of bzrlib
corresponds to the version of Bazaar. Strictly speaking, you can find this version in the output of the bzr version
command, or programmatically with:
$ python -c 'import bzrlib; print bzrlib.version_info[0:3]' (2, 5, 0)
The plugin should expose its own version by using the version_info
variable. For example:
version_info = (1, 0, 0, 'dev', 0)
Although it's not a requirement, it's probably a good idea to adopt the same convention for setting the version, as explained in the __init__.py
file of bzrlib
itself:
# same format as sys.version_info: "A tuple containing the five components of # the version number: major, minor, micro, releaselevel, and serial. All # values except releaselevel are integers; the release level is 'alpha', # 'beta', 'candidate', or 'final'. The version_info value corresponding to the # Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a # releaselevel of 'dev' for unreleased under-development code.
The value of this variable is used, for example, in the output of the bzr plugins
command.
Although some plugins may have their entire code in the __init__.py
file itself, it is more common and often more optimal that the main code is separated into other *.py
files. In this case, when loading the modules with the main functionality, we must use the absolute import path. For example:
def test_suite(): from bzrlib.plugins.customlog import tests return tests.test_suite()
To make sure that Bazaar can resolve the absolute import path bzrlib.plugins.customlog
, it is a common practice to add this simple check:
if __name__ != 'bzrlib.plugins.customlog': raise ImportError( 'The customlog plugin must be installed as' ' bzrlib.plugins.customlog not %s' % __name__)
In this way, the plugin will work only if installed in the directory customlog
, otherwise Bazaar will raise an exception and abort loading the plugin. The exception raised is visible in all the Bazaar commands, as the __init__.py
file is always loaded for all plugins.
In order to hook into Bazaar's architecture, the plugin must register its methods appropriately so that Bazaar can discover them. In the following sections, we will show, with examples, how to register the following types of functionality:
To register a new command, you must use the method bzrlib.commands.plugin_cmds.register_lazy
. For example:
from bzrlib.commands import plugin_cmds plugin_cmds.register_lazy( 'cmd_summary', [], 'bzrlib.plugins.summary.cmd_summary')
The register_lazy
method takes three parameters:
Command
classIn __init__.py
, we only register the command so that Bazaar knows about it.
Note that the method to register the command is named
register_lazy
. At this point, Bazaar knows that such command exists, but it will not load the implementation until it is really used. The command will be listed in the output of bzr help
commands, but its implementation will only be loaded when executing the command itself with bzr summary
.
You can view the complete built-in documentation of the register_lazy
method by using the Python shell:
$ python >>> import bzrlib.commands >>> help(bzrlib.commands.plugin_cmds.register_lazy)
To register a new log format, you must use the method bzrlib.log.log_formatter_registry.register_lazy
. For example:
from bzrlib.log import log_formatter_registry log_formatter_registry.register_lazy( 'custom1', 'bzrlib.plugins.customlog.custom1', 'Custom1LogFormatter', 'Custom1 log format' )
The register_lazy
method takes several parameters:
In __init__.py
, we only register the log format so that Bazaar knows about it.
Notice that the method to register the command is named register_lazy
. At this point, Bazaar knows that such log format exists, but it will not load the implementation until it is really used. The log format will be listed in the output of bzr help log
or bzr log --help
, but its implementation will only be loaded when executing bzr log
with the --custom1
or --log-format=custom1
flags.
The log_formatter_registry.register_lazy
method has more optional parameters. You can view the complete built-in documentation by using the Python shell:
$ python >>> import bzrlib.log >>> help(bzrlib.log.log_formatter_registry.register_lazy)
The method to register a hook depends upon the type of the hook. As explained in bzr help hooks
, the general format of registering hooks is as follows:
yyy.hooks.install_named_hook_lazy("xxx", …)
Here, yyy
is the hook class, and xxx
is the hook type. For example, BranchHook
is a hook class to plug into the steps performed during branch operations. A hook class can include several hook types; the BranchHook
hook class includes the following, for example:
For a complete list of hook classes and hook types, see bzr help hooks
. In this section, we will walk through an example by using the BranchHook
hook class and the post_commit
hook type.
By following the preceding pattern, we can register a post_commit
hook as follows:
from bzrlib.branch import Branch Branch.hooks.install_named_hook_lazy( 'post_commit', 'bzrlib.plugins.appendlog.main', 'appendlog', 'Append commit log to a configured file' )
The install_named_hook_lazy
method takes several parameters:
bzr hooks
See the Creating a hook section in this chapter, for more explanation about hooks. In the __init__.py
file, we only register the hook
method so that Bazaar knows about it.
Notice that the method to register the command is named install_named_hook_lazy
. At this point, Bazaar knows that such a hook exists, but it will not load the implementation until it is really used. The hook will be listed in the output of bzr help appendlog
or bzr hooks
, but its implementation will only be loaded when the hook is triggered; in this example, by a post_commit
action.
You can view the complete built-in documentation of branch hooks by using the Python shell:
$ python >>> import bzrlib.branch >>> help(bzrlib.branch.Branch.hooks)
If you want to implement other kinds of functionalities not explained here, the best way to get started is to find the implementation of a similar functionality in the plugins shipped with Bazaar inside BZRDIR/plugins
, or other plugins, or even core Bazaar subpackages and modules in BZRDIR/*
. Using a similar functionality as an example, try to figure out what needs to be registered in __init__.py
, and what can be put in the other files that are loaded only when necessary.
Bazaar has a framework to perform self-tests of plugins. In order to enable self-tests for the plugin, you must define a test_suite
method in __init__.py
. For example:
def test_suite(): from bzrlib.plugins.customlog import tests return tests.test_suite()
The method must return an instance of the unittest.TestSuite
class, and should include all the unit test cases and test suites to run for the plugin. As usual, in order to keep __init__.py
as fast as possible, the test_suite
method body should be as short as possible, and should only do minimal initialization.
Although we violate the general Python best practice of placing imports near the top of the file, we have a good reason to do so. Defining the test_suite
method is important to let Bazaar know of the test suites, and the implementation should only be loaded when we actually want to perform the self-tests.
Throughout this section, we used the lazy registration methods in all the examples. The reason is that every time you run a bzr
command, Bazaar will load the __init__.py
file of all the Python packages it finds on the plugin path, even if some plugins might not be used. This is the price of the great flexibility—plugins can extend and modify all the aspects of Bazaar, and since there is no way to know in advance what a plugin might do, Bazaar has to load the __init__.py
files in order to let the plugins register their functions and hook into Bazaar's architecture.
By using the lazy registration methods, we make sure that if a plugin is not used during a given bzr
operation, then __init__.py
is the only file that gets loaded and nothing else. When writing __init__.py
, you should always be careful to only load what is essential for the registration of the plugin, and leave the main implementation in the other files that are only loaded when the plugin is actually used. Otherwise, the plugin will cause slowness in all bzr
commands, even the ones that don't use the functionality of that plugin.
Following the implementation guidelines, you can implement and organize the main functionality of the plugin more or less freely within the plugin directory. However, in terms of coding style and certain aspects of programming in Python, there is a relatively long list of guidelines, strongly recommended by the documentation at http://doc.bazaar.canonical.com/developers/code-style.html.
As this document contains many specificities, which might change over time, it is best to find the latest version and read it carefully before you begin to work on a plugin. Here, we will add only a few tips not mentioned in the documentation.
In terms of coding style, PEP8 is the baseline, with a few additional rules explained in the guidelines. An easy way to create a PEP8-compliant code is by using the pep8
utility. This tool checks all the Python files in the specified directories and their subdirectories, and warns of all PEP8 violations. It is also a good idea to review and adjust the settings in your Python IDE, as it may have options to make it easier to create PEP8-compliant code in the first place, rather than fixing violations later.
Another useful tool to validate the Python code and catch common mistakes is pyflakes
, typically in a package with the same name. It checks all the Python files in the specified directories and their subdirectories, and warns of the various types of common Python programming errors and best practice violations.
These are only additional tips. For a detailed list of guidelines, always read the up-to-date version of the Bazaar coding style guide.
Testing is crucial. Some consider untested code broken code. Especially, if you intend to share your plugin with others, then you should implement unit tests to make it easy to verify it works correctly.
Bazaar has a selftest
command to run unit tests defined in its core packages and in installed plugins. In order to use Bazaar's testing framework, your environment and the implementation of the unit tests must meet the following requirements:
testtools
Python library to run unit tests. Install it using your system's package manager or pip
.TestSuite
instances of the unittest
Python package__init__.py
file at the top-level directory of the plugin must define a test_suite
method, which takes no parameters and returns a TestSuite
instance with all the tests to perform when running the self-tests for the pluginA common way to organize the unit test implementation is as follows:
tests
.tests/test_*.py
file, named appropriately in a way to reflect what is being tested in each file.tests/__init__.py
with a test_suite
method, which builds a TestSuite
object by using all the test suites in the tests/test_*.py
implementations.__init__.py
file of the plugin, delegate the test_suite
method call to the method in tests/__init__.py
.For example, in the customlog
plugin, Git-specific unit test suites are defined in tests/test_git.py
, as follows:
from unittest import TestLoader from bzrlib.tests import TestCaseInTempDir def test_suite(): return TestLoader().loadTestsFromName(__name__) class TestGitLogFormat(TestCaseInTempDir): pass
The test suites in this file are loaded by tests/__init__.py
as part of the complete test suite for the entire plugin:
from bzrlib.tests import TestLoader def test_suite(): module_names = [__name__ + '.' + x for x in [ 'test_git', ]] loader = TestLoader() return loader.loadTestsFromModuleNames(module_names)
The complete test suite is registered inside the top-level __init__.py
file, as follows:
def test_suite(): from bzrlib.plugins.customlog import tests return tests.test_suite()
By default, the bzr selftest
command runs all the unit tests defined within Bazaar. This could take a long time. To run only some of the unit tests, you can specify the import path of the plugin by using the -s
flag. For example:
$ bzr selftest -s bzrlib.plugins.customlog bzr selftest: /Users/janos/virtualenv/bzr/bin/bzr /Users/janos/virtualenv/bzr/lib/python2.7/site-packages/bzrlib bzr-2.5.0 python-2.7.3 Darwin-10.8.0-i386-64bit ---------------------------------------------------------------------- Ran 1 test in 0.176s OK
To see the list of tests that would be run you can use the --list-only
flag. For example:
$ bzr selftest -s bp.customlog --list-only bzrlib.plugins.customlog.tests.test_git.TestGitLogFormat.test_format
Bazaar includes several helper classes for performing unit tests on branches. Unfortunately, these helper classes are not well documented; the best place to learn about them is by studying the unit tests in similar plugins or reading the built-in documentation in Python. The most commonly used helper classes are TestCaseInTempDir
and TestCaseWithTransport
.
If you intend to install the plugin system-wide, or share it with other people, you should consider writing a setup.py
script. The Plugin API page in the following documentation explains how to write this file, and includes a complete example:
http://doc.bazaar.canonical.com/developers/plugin-api.html
Apart from a setup()
method at the module scope, the file should define a number of bzr_*
variables, most importantly the following:
bzr_plugin_name
: This specifies the name of the plugin in the same way as you named the directory of the pluginbzr_plugin_version
: This is the same as version_info
in __init__.py
bzr_minimum_version
: This is the same as the minimum API version required in __init__.py
, for example, (2, 5, 0)
bzr_commands
: If the plugin adds any new commands, this variable specifies the list of the command names, for example, ['summary']
bzr_transports
: If the plugin adds any new transports, this variable specifies the list of their names, for example, ['hg+ssh://']
These are only the most commonly used variables to define; for the complete list, see the documentation. Any missing variables will be given default values.
Finally, the script should call the
setup()
method of the distutils.core
module with appropriate parameters. It is easiest to use a simple example as a template; for example, the one included in the documentation of our sample summary plugin:
#!/usr/bin/env python2.6 from distutils.core import setup bzr_commands = [ 'summary', ] bzr_plugin_version = (1, 0, 0, 'dev', 0) bzr_minimum_version = (2, 5, 0) if __name__ == '__main__': setup( name='summary', description='Show brief summary of a branch and its files', keywords='plugin bzr summary', version='1.0.0dev0', url='lp:~bzrbook/bzrbook-examples/bzr-summary', download_url='http://launchpad.net/' '~bzrbook/bzrbook-examples/bzr-summary', license='Creative Commons', author='Janos Gyerik', author_email='[email protected]', long_description=""" Show brief summary of a branch and its files """, package_dir={ 'bzrlib.plugins.summary': '.', 'bzrlib.plugins.summary.tests': 'tests' }, packages=[ 'bzrlib.plugins.summary', 'bzrlib.plugins.summary.tests' ] )
To test that your setup.py
file is correct and working, try to install it in your user directory with the following command:
$ python setup.py install --user
The user directory may depend upon your system; usually it is $HOME/.local/lib/python2.6/site-packages
, and thus the plugin will be installed in the directory $HOME/.local/lib/python2.6/site-packages/bzrlib/plugins
. However, if bzr
is not installed in the user's PATH, then you have to set the BZR_PLUGIN_PATH
variable so that Bazaar includes this custom plugins directory when searching for plugins. For example:
$ export BZR_PLUGIN_PATH=$HOME/.local/lib/python2.6/site-packages/bzrlib/plugins $ bzr summary
Before you begin to reinvent the wheel, it is probably a good idea to have a look at what exists already. There are two plugin listings in the documentation:
The list on the Plugins Guide is generated based upon the lp:bzr-alldocs
project, while the plugins registry is a wiki page. The Plugins Guide lists only the commonly used plugins; for a complete list of registered plugins, see the plugin-registry.ini
file inside the lp:bzr-alldocs
project.
If you would like to share your plugin with others, it is a good idea to register it in the official plugin registry.
It is important to clarify the license of the plugin. Although it is recommended to use the same license as Bazaar itself, GPL v2, it is not mandatory.
Before registering the plugin, you should ensure its quality. Make sure to review the following points:
README
file__init__.py
filepep8
toolpyflakes
toolThat's not a short list, but if you are to present your work to a wide audience and if it is to pass the rigorous checks of the Plugins Guide maintainers, it had better be good.
Registering a plugin involves branching from the lp:bzr-alldocs
project, editing the main registry file listing all the plugins, and proposing the branch for merging:
lp:bzr-alldocs
with bzr branch lp:bzr-alldocs
plugins-registry.ini
—read the instructions carefully at the top, and add a section for your plugin appropriatelybzr push lp:~youruser/bzr-alldocs/added-plugin-NAME
A project maintainer will review your merge proposal and plugin, and possibly get back to you with questions. When accepting the merge proposal, the project maintainer may decide to add the plugin to a category listed on the Plugins Guide page, so that the plugin will appear on the following page after the site files are regenerated:
3.138.37.151