Skip to content

Building

github-actions[bot] edited this page Nov 9, 2026 · 5 revisions

Building EDLD

Running from source

EDLD runs directly from a checkout with no build step.

git clone https://github.com/indevlin/EDLD.git
cd EDLD
pip install -r requirements.txt
python edld.py            # EDCARS, the desktop window (default)
python edld.py --dashg    # desktop window, Dashboard layout (or --gui)
python edld.py --dasht    # terminal dashboard (or --tui)
python edld.py --term     # scrolling event log (or --text)

The interfaces read the same journal, the same config and the same per-commander data, so you can switch freely between them.

Optional dependencies

  • PySide6 is needed only for the desktop window — EDCARS, the default, and --dashg. Without it the terminal modes work normally, a plain launch opens the terminal dashboard and says why, and --edcars or --dashg exits with an explanatory message.
  • Textual is needed only for --dasht. Without it --term still works.
  • cryptography and segno are needed only for server mode (-s, the EDAM mobile app; see SERVER.md). Without cryptography the server says so and EDLD carries on without it; without segno --pair prints the code as text instead of a QR code.
  • psutil is needed only for session management. Install it from your distro package manager rather than pip — it has C extensions: sudo pacman -S python-psutil / sudo apt install python3-psutil / sudo dnf install python3-psutil.

Building a binary

pip install -r requirements-dev.txt
pyinstaller packaging/edld.spec --noconfirm --clean

The whole release build, locally

scripts/build_local.sh runs the same sequence the Release workflow runs, on this machine: the preflight on the checkout, the PyInstaller build, both smoke tests, the archive with the license texts, and the SHA-256 file.

scripts/build_local.sh                 # build, test, package into out/
scripts/build_local.sh --no-package    # build and test only
scripts/build_local.sh --dir           # directory layout instead of onefile
scripts/build_local.sh --skip-tests    # skip the smoke tests
scripts/build_local.sh --sign          # also sign, using SIGNING_KEY
scripts/build_local.sh --server        # EDLD-server: no dashboards

--server builds EDLD-server, the same program without either dashboard — no Qt, no Textual, about a fifth of the size — named EDLD-<version>-server-…. By hand it is EDLD_VARIANT=server pyinstaller packaging/edld.spec. See SERVER.md.

Artefacts land in out/, named exactly as the release ones are. On Windows run it from Git Bash or MSYS2. PyInstaller does not cross-compile, so this builds for the platform it runs on — three machines are still needed for a full set.

It is also the fastest way to tell a broken tree from a broken runner: if this passes and CI does not, the difference is the runner, not the code.

The artefact lands in dist/ — dist/EDLD on Linux, dist/EDLD.exe on Windows, dist/EDLD.app on macOS.

Verify it starts, and that both interfaces load:

./dist/EDLD --version     # should print the contents of ./version
./dist/EDLD --selftest    # imports each front end and reports

Both matter. --version proves the process starts; --selftest proves the interfaces are actually intact. A packaged build can start perfectly and still be missing a module that only fails when the dashboard is drawn — Textual resolves its widgets through a runtime __getattr__, which static analysis cannot follow, so this has happened. The release workflow runs both on every platform.

Linux runtime libraries

Qt needs a handful of X libraries present at runtime. On a bare CI image:

sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-cursor0 \
  libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 libxcb-randr0 \
  libxcb-render-util0 libxcb-xinerama0

Add xvfb if you want to run the GUI headlessly.

Relinking against your own Qt

This is the LGPLv3 4(d)(1) route, and it is deliberately easy — see LICENSING.md.

pip install --force-reinstall /path/to/your/PySide6-wheel
pyinstaller packaging/edld.spec --noconfirm --clean

Nothing in the spec pins a Qt version or links it statically, so the binary you get is equivalent to the official one but built against your Qt.

A directory layout instead of one file

The spec produces a single file. If you would rather have the Qt shared libraries as visibly separate files, build with -D:

pyinstaller packaging/edld.spec --noconfirm --clean -D

What the bundle carries beyond the code

Three things are shipped as data because the code alone is not enough:

  • components/ — the component sources, in addition to being compiled in. core/plugin_loader.py discovers components by globbing that directory and loads each by file path, giving every one its own namespace and a sandboxed open(). That needs real files on disk, which a one-file build does not otherwise have.
  • A CA bundle (certifi) — a frozen binary carries its own OpenSSL, built with the build machine's certificate paths compiled in. Those paths rarely exist on the target machine, so without a bundled store every HTTPS call fails verification and CAPI, EDDN, EDSM, EDAstro, Inara and Spansh all stop working with no visible error. core/certs.py points OpenSSL at the bundled copy at startup.
  • licenses/ — required to accompany the binary by LGPLv3 section 4(b).

Each has already been the cause of a build that looked fine and was not.

Why the spec lists so many hidden imports

core/plugin_loader.py discovers components by walking components/ at runtime, and both front ends resolve their dashboard blocks by name. None of that is visible to PyInstaller's static analysis, so every component and block is named explicitly in packaging/build_common.py, which enumerates the directories rather than hard-coding a list.

A component missing from that list produces a binary that builds cleanly, starts cleanly, and shows an empty dashboard. That failure mode is the reason the release workflow smoke-tests every artefact before publishing it.

Textual is collected wholesale for the same reason from the other direction: textual.widgets imports lazily by a name built at runtime, so nothing static analysis can see refers to most of its widgets.

Releases

Tag with a bare YYYYMMDD datestamp matching the version file, as a signed tag:

echo 20261101 > version
python3 sheets/build_bundle.py        # the sheet bundle carries the version
git commit -am "Release 20261101"
git tag -s 20261101 -m "Release 20261101"
git push origin main --tags

The authoritative repository is git.indevlin.com, mirrored to GitHub, where the workflow runs; see SIGNING.md.

The Release workflow checks the tag matches the version file, verifies the checkout contains everything the build needs and that the sheet bundle was rebuilt for this version, then builds and smoke-tests EDLD for Linux, Windows and macOS and EDLD-server for Linux and Windows, signs the checksum manifest, and publishes a release with notes taken from CHANGELOG.md.

A version with a suffix — 20261101-rc1 — is published as a prerelease. To build everything without publishing, run the workflow manually from the Actions tab with dry_run left on. That works from any branch, dev included: the artefacts are named from the version file and attached to the run.

Survey sheets are not released this way at all. They follow a branch — main by default — so merging to main releases sheet code, and a test sheet can follow dev to try it first. See Trying sheet code before it is released.

Clone this wiki locally