-
Zohreh MOSTAANI authoredZohreh MOSTAANI authored
Additional Considerations
These instructions describe how create new packages for either Bob_ or BEAT_ and provides information on how to generate a complete, but empty package from scratch.
Note
If you'd like to update part of your package setup, follow similar instructions and then copy the relevant files to your existing setup, overriding portions you know are correct.
Warning
These instructions may change as we get more experience in what needs to be changed. In case that happens, update your package by generating a new setup and copying the relevant parts to your existing package(s).
Unit tests
Writing unit tests is an important asset on code that needs to run in different platforms and a great way to make sure all is OK. Test units are run with nose_. To run the test units on your package call:
$ ./bin/nosetests -v
bob.example.library.test.test_reverse ... ok
----------------------------------------------------------------------
Ran 1 test in 0.253s
OK
This example shows the results of the tests in the bob.example.project
package. Ideally, you should
write test units for each function of your package ...
Note
You should put additional packages needed for testing (e.g. nosetests
)
in the test-requirements.txt
file.
Continuous integration (CI)
Note
This is valid for people at Idiap (or external bob contributors with access to Idiap's gitlab)
Note
Before going into CI, you should make sure that your pacakge has a gitlab repository. If not, do the following in your package root folder:
$ git init
$ git remote add origin git@gitlab.idiap.ch:bob/`basename $(pwd)`
$ git add bob/ buildout.cfg COPYING doc/ MANIFEST.IN README.rst requirements.txt setup.py version.txt
$ git commit -m '[Initial commit]'
$ git push -u origin master
Copy the appropriate yml template for the CI builds:
# for pure python
$ curl -k --silent https://gitlab.idiap.ch/bob/bob.admin/raw/master/templates/ci-for-python-only.yml > .gitlab-ci.yml
# for c/c++ extensions
$ curl -k --silent https://gitlab.idiap.ch/bob/bob.admin/raw/master/templates/ci-for-cxx-extensions.yml | tr -d '\r' > .gitlab-ci.yml
Add the file to git:
$ git add .gitlab-ci.yml
The ci file should work out of the box. It is long-ish, but generic to any package in the system.
You also need to enable the following options - through gitlab - on your project:
- In the project "Settings" page, make sure builds are enabled
- If you have a private project, check the package settings and make sure that the "Deploy Keys" for our builders (all conda-* related servers) are enabled
- Visit the "Runners" section of your package settings and enable all conda runners, for linux and macosx variants
- Go into the "Variables" section of your package setup and add common variables corresponding to the usernames and passwords for uploading wheels and documentation tar balls to our (web DAV) server, as well as PyPI packages. You can copy required values from [the "Variables" section of bob.admin](https://gitlab.idiap.ch/bob/bob.admin/variables). N.B.: You must be logged into gitlab to access that page.
- Make sure to disable the service "Build e-mails" (those are very annoying)
- Setup the coverage regular expression under "CI/CD pipelines" to have the value ^TOTAL.*s+(d+%)$, which is adequate for figuring out the output of coverage report
Python package namespace
We like to make use of namespaces to define combined sets of functionality that go well together.
Python package namespaces are explained in details here together with implementation details.
For bob packages, we usually use the bob
namespace, using several sub-namespaces such as bob.io
, bob.ip
, bob.learn
, bob.db
or (like here) bob.example
.
The scripts you created should also somehow contain the namespace of the package. In our example,
the script is named bob_example_project_version.py
, reflecting the namespace bob.example
Distributing your work
To distribute a package, we recommend you use PyPI_. Python Packaging User Guide contains details and good examples on how to achieve this. Moreover, you can provide a conda_ package for your PyPI_ package for easier installation. In order to create a conda_ package, you need to create a conda_ recipe for that package.
For more detailed instructions on how to achieve this, please see the guidelines on bob.template.
Buildout.cfg in more details
Some notes on buildout
To be able to develop a package, we first need to build and install it locally. While developing a package, you need to install your package in development mode so that you do not have to re-install your package after every change that you do in the source. zc.buildout_ allows you to exactly do that.
Note
zc.buildout_ will create another local environment from your conda_ environment but unlike conda_ environments this environment is not isolated rather it inherits from your conda_ environment. This means you can still use the libraries that are installed in your conda_ environment. zc.buildout_ also allows you to install PyPI_ packages into your environment. You can use it to install some Python library if it is not available using conda_. Keep in mind that to install a library you should always prefer conda_ but to install your package from source in development mode, you should use zc.buildout_.
zc.buildout_ provides a buildout
command. buildout
takes as input a
"recipe" that explains how to build a local working environment. The recipe, by
default, is stored in a file called buildout.cfg
.
.. note:
Buildout by default looks for ``buildout.cfg`` in your current folder and
uses that configuration file. You can specify a different config file with
the ``-c`` option:
.. code:: sh
$ buildout -c develop.cfg
Important
Once buildout
runs, it creates several executable scripts in a local
bin
folder. Each executable is programmed to use Python from the conda
environment, but also to consider (prioritarily) your package checkout.
This means that you need to use the scripts from the bin
folder instead
of using its equivalence from your conda environment. For example, use
./bin/python
instead of python
.
buildout
will examine the setup.py
file of packages using setuptools_
and will ensure all build and run-time dependencies of packages are available
either through the conda installation or it will install them locally without
changing your conda environment.
The configuration file is organized in several sections, which are indicated
by []
, where the default section [buildout]
is always required. Some of
the entries need attention.
- The first entry are the
eggs
. In there, you can list all python packages that should be installed. These packages will then be available to be used in your environment. Dependencies for those packages will be automatically managed, as long as you keepbob.buildout
in your list ofextensions
. At least, the current package needs to be in theeggs
list. - The
extensions
list includes all extensions that are required in the buildout process. By default, onlybob.buildout
is required, but more extensions can be added (more on that later). - The next entry is the
develop
list. These packages will be installed development mode from the specified folder.
The remaining options define how the (dependent) packages are built. For
example, the debug
flag defined, how the C++ code in
all the (dependent) packages is built. For more information refer to C/C++ modules in your package in bob.extension documentation. The verbose
options handles the
verbosity of the build. When the newest
flag is set to true
, buildout
will install all packages in the latest versions, even if an older version is
already available.
Note
We normally set newest = False
to avoid downloading already installed
dependencies. Also, it installs by default the latest stable version of the
package, unless prefer-final = False
, in which case the latest
available on PyPI, including betas, will be installed.
Warning
Compiling packages in debug mode (debug = true
) will make them very
slow. You should only use this option when you are developing and not for
running experiments or production.
When the buildout command is invoked it will perform the following steps:
- It goes through the list of
eggs
, searched for according packages and installed them locally. - It populates the
./bin
directory with all theconsole_scripts
that you have specified in thesetup.py
.
Important
One thing to note in package development is that when you change the entry
points in setup.py
of a package, you need to run buildout
again.
Using mr.developer
One extension that may be useful is `mr.developer`_. It allows to develop
several packages at the same time. This extension will allow
buildout to automatically check out packages from git repositories, and places
them into the ./src
directory. It can be simply set up by adding
mr.developer
to the extensions section.
In this case, the develop section should be augmented with the packages you would like to develop. There, you can list directories that contain Python packages, which will be build in exactly the order that you specified. With this option, you can tell buildout particularly, in which directories it should look for some packages.
[buildout]
parts = scripts
extensions = bob.buildout
mr.developer
newest = false
verbose = true
debug = false
auto-checkout = *
develop = src/bob.extension
src/bob.blitz
eggs = bob.extension
bob.blitz
[scripts]
recipe = bob.buildout:scripts
dependent-scripts = true
[sources]
bob.extension = git https://gitlab.idiap.ch/bob/bob.extension
bob.blitz = git https://gitlab.idiap.ch/bob/bob.blitz
A new section called [sources]
appears, where the package information for
`mr.developer`_ is initialized. For more details, please read its
documentation. mr.developer does
not automatically place the packages into the develop
list (and neither in
the eggs
), so you have to do that yourself.
With this augmented buildout.cfg
, the buildout
command will perform the
following steps:
- It checks out the packages that you specified using
mr.developer
. - It develops all packages in the
develop
section (it links the source of the packages to your local environment). - It will go through the list of
eggs
and search for according packages in the following order:- In one of the already developed directories.
- In the python environment, e.g., packages installed with
pip
. - Online, i.e. on PyPI_.
- It will populate the
./bin
directory with all theconsole_scripts
that you have specified in thesetup.py
. In our example, this is./bin/bob_new_version.py
.
The order of packages that you list in eggs
and develop
are important
and dependencies should be listed first. Especially, when you want to use a
private package and which not available through `pypi`_. If you do not specify
them in order, you might face with some errors like this:
Could not find index page for 'a.bob.package' (maybe misspelled?)
If you see such errors, you may need to add the missing package to eggs
and
develop
and sources
(of course, respecting the order of
dependencies).
Your local environment
After buildout has finished, you should now be able to execute
./bin/python
. When using the newly generated ./bin/python
script, you
can access all packages that you have developed, including your own package:
$ ./bin/python
>>> import bob.blitz
>>> bob.blitz # should print from '.../awesome-project/src/bob.blitz/...'
<module 'bob.blitz' from 'awesome-project/src/bob.blitz/bob/blitz/__init__.py'>
>>> print(bob.blitz.get_config())
bob.blitz: 2.0.15b0 [api=0x0202] (awesome-project/src/bob.blitz)
* C/C++ dependencies:
- Blitz++: 0.10
- Boost: 1.61.0
- Compiler: {'version': '4.8.5', 'name': 'gcc'}
- NumPy: {'abi': '0x01000009', 'api': '0x0000000A'}
- Python: 2.7.13
* Python dependencies:
- bob.extension: 2.4.6b0 (awesome-project/src/bob.extension)
- numpy: 1.12.1 (miniconda/envs/bob3py27/lib/python2.7/site-packages)
- setuptools: 36.4.0 (miniconda/envs/bob3py27/lib/python2.7/site-packages)
Everything is now setup for you to continue the development of the packages. Moreover, you can learn more about |project| packages and learn to create new ones in .
Anatomy of a new package
bob.<awesome-project>
+-- bob
+-- __init__.py #namespace init for "bob"
+-- conda
+-- meta.yaml
+-- doc
+-- img
+-- conf.py
+-- index.rst
+-- links.rst
+-- .gitignore
+-- .gitlab-ci.yml
+-- buildout.cfg
+-- COPYING
+-- MANIFEST.IN
+-- README.rst
+-- requirements.txt
+-- setup.py
+-- version.txt
There is a folder named conda that includes a file meta.yaml. As explained earlier this files includes the information used to prepare a proper conda environment.
The folder named bob which should only include a file __init__.py at this stage is where you will put all your new code and functionality in.
The folder named doc includes the minimum necessary information for building package documentation. The file conf.py is used by sphinx to build the documentation.
.gitlab-ci.yml includes the information about building packages on the ci. We will talk about it later.
COPYING??? MANIFEST.IN???
Continuous Integration and Deployment (CI)
If you'd like just to update CI instructions, copy the file .gitlab-ci.yml
from bob/devtools/templates/.gitlab-ci.yml
overriding your existing
one:
$ curl -k --silent https://gitlab.idiap.ch/bob/bob.devtools/raw/master/bob/devtools/templates/.gitlab-ci.yml > .gitlab-ci.yml
$ git add .gitlab-ci.yml
$ git commit -m '[ci] Updated CI instructions' .gitlab-ci.yml
The ci file should work out of the box, it is just a reference to a global configuration file that is adequate for all packages inside the Bob_/BEAT_ ecosystem.
You also remember to enable the following options on your project:
- In the project "Settings" page, make sure builds are enabled
- Visit the "Runners" section of your package settings and enable all runners with the docker and macosx tags.
- Setup the coverage regular expression under "CI/CD pipelines" to have the value ^TOTAL.*s+(d+%)$, which is adequate for figuring out the output of coverage report
New unexisting dependencies
If your package depends on third-party packages (not Bob_ or BEAT_ existing
resources) that are not in the CI, but exist on the conda defaults
channel,
you should perform some extra steps:
-
Add the package in the
meta.yml
file of bob-devel inbob/bob.conda/conda/bob-devel
:requirements: host: - python {{ python }} - {{ compiler('c') }} - {{ compiler('cxx') }} # Dependency list of bob packages. Everything is pinned to allow for better # reproducibility. Please keep this list sorted. It is recommended that you # update all dependencies at once (to their latest version) each time you # modify the dependencies here. Use ``conda search`` to find the latest # version of packages. - boost 1.65.1 - caffe 1.0 # [linux] - click 6.7 - click-plugins 1.0.3 - .. - [your dependency here]
-
At the same file, update the version with the current date, in the format preset.
package: name: bob-devel version: 2018.05.02 <-- HERE
-
Update the
beat-devel
andbob-devel
versions in themeta.yml
file insidebob/bob.conda/conda/beat-devel
:package: name: beat-devel version: 2018.05.02 <-- HERE [...] requirements: host: - python {{ python }} - bob-devel 2018.05.02 <-- HERE - requests 2.18.4
-
Update the
conda_build_config.yaml
inbob/bob.devtools/bob/devtools/data/conda_build_config.yaml
with your dependencies, and with the updated version of bob-devel and beat-devel. See this here and this MR here for concrete examples on how to do this.Note
This step should be performed after bob.conda's pipeline on master is finished (i.e. perform steps 1 to 3 in a branch, open a merge request and wait for it to be merged, and wait for the new master branch to be "green").
Conda recipe
The CI system is based on conda recipes to build the package. The recipes are
located in the conda/meta.yaml
file of each package. You can start
to modify the recipe of each package from the template generated by bdt
template
command as explained above, for new packages.
The template meta.yaml
file in this package is up-to-date. If you see a
Bob_ or BEAT_ package that does not look similar to this recipe, please let us
know as soon as possible.
You should refrain from modifying the recipe except for the places that you are asked to modify. We want to keep recipes as similar as possible so that updating all of them in future would be possible by a script.
Each recipe is unique to the package and need to be further modified by the
package maintainer to work. The reference definition of the meta.yaml
file
is https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html.
The meta.yaml
file (referred to as the recipe) will contain duplicate
information that is already documented in setup.py
, requirements.txt
,
and, eventually, in test-requirements.txt
. For the time being you have to
maintain both the meta.yaml
file and the other files.
Let's walk through the conda/meta.yaml
file (the recipe) that you just
created and further customize it to your package. You need to carry out all
the steps below otherwise the template meta.yaml
is not usable as it is.