make build
make run
make install
make test
Performance smoke tests are skipped by default. Run them with make test-perf to catch order-of-magnitude regressions in the per-feature pipeline.
The GIFs in README.md (interactive map tool and Processing algorithm) are recorded by a scripted QGIS session, so they can be regenerated after UI changes:
make gifs # all scenarios
make gifs SCENARIOS="distance" # just one (the scenario name is the GIF name)
make gifs THEME=light # light QGIS theme (default: dark)
GIFs are written to build/gifs/ (gitignored); upload them to the pptl_images repo, which the README links to. The setup lives in tools/record_gifs/:
Dockerfile extends the project image with Xvfb, openbox, xdotool and ffmpeg.qgis_startup.py runs inside QGIS: builds the demo layers (a separate, more varied scene for Processing scenarios), loads the plugin, exports screen positions of features and toolbar buttons, and keeps ui.json updated with positions of menus, the Processing toolbox and algorithm widget, dialogs, combo-box popups and layer-tree checkboxes.driver.py moves a real X cursor through each scenario, targeting static points by name and transient UI by ui.json key (e.g. menu:Toolbox, param:DISTANCE, button:Run). Add a scenario by adding a function there, its unrecorded preconditions to SETUP, and its name to SCENARIOS in the Makefile (and, for a Processing scenario, to PROCESSING_SCENARIOS in qgis_startup.py, otherwise it is recorded on the interactive scene).record.sh ties it together inside the container and fails if the plugin raised any Python error during the recording.Gotchas: the QGIS image is amd64-only, so on Apple Silicon it runs emulated (roughly a minute per scenario). D-Bus is disabled on purpose, otherwise a GNOME keyring password prompt blocks QGIS startup. The install step needs about 120 MB free in Docker’s disk.
These instructions are specific to PyCharm.
uv Python interpreter. uv sync
make install-dev
127.0.0.153100<absolute/path/to/repo>=/pptl (replace the left side with your local clone’s absolute path)tests/test_main_functionality.py:
# pydevd_pycharm.settrace("host.docker.internal", port=53100, stdoutToServer=True, stderrToServer=True)
import pydevd_pycharm to tests/test_main_functionality.py.pydevd-pycharm and pydevd into the Python interpreter bundled with QGIS. On macOS:
/Applications/QGIS.app/Contents/MacOS/bin/python3 -m pip install pydevd-pycharm pydevd
On Linux/Windows, invoke pip from the QGIS-bundled Python (e.g., the python3 shipped under the QGIS install directory).
PolygonsParallelToLine/src/pptl.py:
# pydevd_pycharm.settrace("127.0.0.1", port=53100, stdoutToServer=True, stderrToServer=True)
import pydevd_pycharm to PolygonsParallelToLine/src/pptl.py.QGIS_PLUGINPATH<absolute/path/to/repo> (the absolute path to your local clone of this repository)CHANGELOG.md’s ## [Unreleased] section has the entries you want shipped. The plugin portal shows them as plain text, so write short bullets without Markdown formatting (no **bold**, *italic* or backticks). Then on main with a clean working tree run:
make tag VERSION=X.Y.Z
This rewrites ## [Unreleased] to ## [X.Y.Z] - YYYY-MM-DD, inserts a fresh empty ## [Unreleased] above it, commits, tags, and pushes both to origin atomically. qgis-plugin-ci requires a 3-part MAJOR.MINOR.PATCH version — ## [1.2] would be silently ignored and the published changelog would be empty.
Release workflow (.github/workflows/release.yaml) runs the tests, checks that QGIS_PLUGIN_TOKEN is set, then publishes to plugins.qgis.org and creates a GitHub Release with the .zip attached.Required GitHub Secrets (one-time setup, repo Settings → Secrets and variables → Actions):
QGIS_PLUGIN_TOKEN: an upload token created on the plugin’s page on plugins.qgis.org (Tokens)REPORT_EMAIL: the address the report dialog’s Send by email… button writes to. The workflow writes it into PolygonsParallelToLine/src/contact.py only for the package, so it never appears in the repository. It is still readable inside the published zip, so prefer a forwarding alias you can replace (e.g. iCloud+ Hide My Email). Local and CI builds leave it empty, which hides the email option.REPORT_ENDPOINT: the URL of the report receiver behind Send report (a Cloudflare Worker, set up as described in tools/report_worker/README.md). Written into contact.py the same way; empty hides the button.Uploading with a token syncs the portal’s about text from metadata.txt on every release. It does not sync name unless a portal admin enables “Allow update name” for the plugin.
(GITHUB_TOKEN is provided automatically by Actions.)
For a staged/experimental release, use a 3-part pre-release tag suffix (e.g. 1.2.0-rc1). The portal will list it as experimental. A 2-part form like 1.2-rc1 is not detected as a pre-release by qgis-plugin-ci and will be published as stable.
The project page is built by .github/workflows/pages.yaml with the jekyll-theme-midnight theme. Its sources live in site/ (config, layout, styles, images); tools/stage_site.sh combines them with README.md (as the home page, from its title down), DEVELOPMENT.md, LICENSE.txt and the plugin icon (as the favicon) into build/site, so nothing else in the repo is published. To preview the site or trial other themes locally:
github-pages gem pins Jekyll 3.9 / Liquid 4.0.3, which calls String#tainted? — removed in Ruby 3.2 — so newer Rubies will not work.
brew install ruby@3.1
echo 'export PATH="/opt/homebrew/opt/ruby@3.1/bin:$PATH"' >> ~/.zshrc
exec zsh
bundle install
make site
The site is served at http://127.0.0.1:4000. Jekyll watches the staged copy, not the sources, so restart make site after editing site/ or README.md.
theme: in site/_config.yml, and restart make site: Jekyll watches content but not config. The Gemfile already pulls in every GitHub-supported theme (architect, cayman, dinky, hacker, leap-day, merlot, midnight, minimal, modernist, primer, slate, tactile, time-machine).The Gemfile also lists several stdlib gems (csv, bigdecimal, base64, logger, mutex_m, ostruct, drb, webrick) that were promoted out of Ruby’s default gem set in 3.0–3.4 and are still required by Jekyll 3.9 / its dependencies.