This document contains important information about Nicotine+ design decisions and development procedures for maintainers, developers and code contributors alike.
Nicotine+ is Python application, originally based on backend code from the PySoulSeek project started in 2001. We only use Python in our codebase, as this allows for running Nicotine+ on almost any system without compiling anything. Developing in a single language is also easier for everyone involved in the project.
We aim to support the oldest Python version still receiving security updates, up to the latest. There is no rush to bump the minimum version requirement, since LTS distributions may use older Python versions, but a smaller range of versions is easier to test.
Nicotine+ and its predecessors PySoulSeek and Nicotine were originally developed with GNU/Linux in mind, at a time when the official Soulseek client only supported Windows. The Nicotine project opted to use GTK as the GUI toolkit, as opposed to wxPython previously used by PySoulSeek. This decision was made due to various issues encountered in wxPython at the time, such as large memory overhead and long compile/build times.
GTK fits our needs, and we have no plans of switching to another toolkit.
Nicotine+ aims to be as portable as possible, providing access to the Soulseek network for people who cannot run the official Soulseek client. Nicotine+ runs on almost any architecture and system available, and has active users on a plethora of different systems. This also means that the introduction of an external software dependency can be an inconvenience for both packagers and users.
Modules included in the Python Standard Library should be preferred whenever possible. Avoid introducing “convenient” and “new hotness” dependencies, if the standard library already includes the required functionality to some degree. If a new dependency is necessary, think about the following points:
The current dependencies for Nicotine+ are described in DEPENDENCIES.md.
Keep it simple. Many applications fall in the trap of adding endless options without thinking about the bigger picture. Nicotine+ is feature-rich, but attempts to provide well-designed, well-integrated features that work out of the box.
The same principle applies when writing code. Try to simplify code as much as possible. Avoid overengineering. We want to write code that is maintainable and readable by humans.
Profiling code changes from time to time is important, to ensure that Nicotine+ performs well and uses fewer system resources. Our goal is to develop a lightweight client that runs well on older hardware, as well as small servers.
Due to Python’s interpreted nature, addressing performance issues can be a challenge. There is no straightforward way of solving every performance issue, but these points generally help:
py-spy is an excellent tool for profiling in real time while running Nicotine+ directly from a local Git folder and starting with:
py-spy top ./nicotine
The console will continuously display a top like view of functions consuming
CPU. Press L
to aggregate by line number, and R
to reset the view.
Verbose logging can be enabled to ease debugging. The following log categories are available:
In order to enable debug logging:
Log Categories
submenu.If you want to log debug messages to file, Menu -> Preferences -> Logging ->
Log debug messages to file
. Remember to disable debug logging when you no
longer need it, since it impacts performance.
The Soulseek network uses its own protocol for interoperability between clients. The protocol is proprietary, and no official documentation is available. Nicotine+’s protocol implementation is developed by observing the behavior of the official Soulseek NS and SoulseekQt clients.
SLSKPROTOCOL.md contains unofficial documentation maintained by the Nicotine+ team.
It is important that all changes pass unit and integration testing. To properly validate that things are working, continuous integration (CI) is implemented using GitHub Actions workflows, which run tests on various platforms for each commit.
Tests are defined in the pynicotine/tests/ folder, and should be expanded to cover larger parts of the client when possible.
Although GitHub Actions runs tests automatically when pushing changes to the repository, it is also possible to run them locally. This can be helpful to quickly test smaller changes, but local testing should not be relied upon for changes that affect packaging or are likely to break on other platforms.
To run unit and integration tests using Python’s unittest module:
python3 -m unittest
To check the code style using pycodestyle:
python3 -m pycodestyle
To perform code linting using Pylint:
python3 -m pylint --recursive=y .
To test building the application:
python3 -m build
Translations are largely handled by Weblate, but certain manual operations need to be performed at times.
When Nicotine+ is translated into a new language, the following should be done:
Update the copyright header of the XX.po
file:
# Copyright (C) 20XX Nicotine+ Translators
# This file is distributed under the same license as the Nicotine+ package.
Remove the PACKAGE VERSION
value of Project-Id-Version
in the XX.po
file:
"Project-Id-Version: \n"
Add the language code to the po/LINGUAS and pynicotine/i18n.py files
The translation template file po/nicotine.pot should be updated after modifying strings in the codebase. To update the template, run the following command:
python3 data/scripts/update_translation_template.py
When translations are modified, Weblate creates a pull request with the changes
within 24 hours. In order to preserve author information for commits, use the
Create a merge commit
option when merging the pull request.
Nicotine+ uses the following versioning scheme: A.B.C
, e.g. 3.3.8
A
is incremented when major changes are made to the user interface that
significantly change the workflow of the application. In practice, this is
unlikely to happen.B
is incremented when significant features or new dependencies are added,
version requirements are increased, or in the case of major changes to
existing code that are deemed too intrusive for a smaller release. The
previous feature branch (e.g. 3.3.x
) is created in case any critical bugs
are discovered while the next planned release is still in development.C
is incremented when bug fixes, performance improvements or smaller
tweaks to existing functionality are made. Low-risk functionality that does
not affect translatable strings can also be added.Release dates are not set in stone, as Nicotine+ development is done by volunteers in their spare time. However, keep the following points in mind:
C
) of each feature branch (B
) has to be very
stable before any dependency requirements are bumped, because legacy users
might end up needing to use an old version for a long time.The following is a step-by-step guide detailing what a Nicotine+ maintainer should do when releasing a new version of Nicotine+.
Update the translation template by running
python3 data/scripts/update_translation_template.py
Ensure that the source distribution (sdist) includes all necessary files to run Nicotine+. A source distribution can be generated by running
python3 -m build --sdist
Add a new release note entry to NEWS.md. Release notes should contain a user-readable list of noteworthy changes since the last release (not a list of commits), as well as a list of closed issues on GitHub.
Ensure that the Windows and macOS packages generated by GitHub Actions still work.
x.x.x
,
e.g. 3.2.1
.packaging/release/
and run
python3 packaging/release/generate_sha256_checksums.py
Generate a stable PPA release for Ubuntu / Debian. First, ensure that the repository mirror on Launchpad is up to date. Once it is, update the contents of the stable build recipe, replacing the previous commit hash with the one used for the release you tagged on GitHub. Then, generate stable builds by pressing Request build(s).
Create a new release on Flathub.