From 2ee22a497aa6aab3b195386591c8d30055d418b7 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 11:30:31 +0300 Subject: [PATCH 01/16] Reinstall deps after doit cleans everything Make sure to reinstall the project dependencies once `doit clean` runs, since it also removes itself. --- Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 217416d..48347cc 100644 --- a/Makefile +++ b/Makefile @@ -50,20 +50,25 @@ test-large: test-large-init ## Run large test set Dockerfile: Dockerfile.env Dockerfile.in poetry run jinja2 Dockerfile.in Dockerfile.env > Dockerfile +.PHONY: poetry-install +poetry-install: + poetry install + + .PHONY: build-clean build-clean: doit clean .PHONY: build-macos-intel -build-macos-intel: build-clean +build-macos-intel: build-clean poetry-install doit -n 8 .PHONY: build-macos-arm -build-macos-arm: build-clean +build-macos-arm: build-clean poetry-install doit -n 8 macos_build_dmg .PHONY: build-linux -build-linux: build-clean +build-linux: build-clean poetry-install doit -n 8 fedora_rpm debian_deb .PHONY: regenerate-reference-pdfs From 9df825db5c68d2243b1c396f26ac134da0fbf1a3 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 11:31:54 +0300 Subject: [PATCH 02/16] debian: Use abbreviated months in changelog Use abbreviated months in the Debian changelog, else we'll have warnings like the following: LINE: -- Freedom of the Press Foundation Mon, 31 March 2025 15:57:18 +0300 dpkg-source: warning: dangerzone/debian/changelog(l5): cannot parse non-conformant date '31 March 20 --- debian/changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 269ced6..847cbda 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,13 +2,13 @@ dangerzone (0.9.0) unstable; urgency=low * Released Dangerzone 0.9.0 - -- Freedom of the Press Foundation Mon, 31 March 2025 15:57:18 +0300 + -- Freedom of the Press Foundation Mon, 31 Mar 2025 15:57:18 +0300 dangerzone (0.8.1) unstable; urgency=low * Released Dangerzone 0.8.1 - -- Freedom of the Press Foundation Tue, 22 December 2024 22:03:28 +0300 + -- Freedom of the Press Foundation Tue, 22 Dec 2024 22:03:28 +0300 dangerzone (0.8.0) unstable; urgency=low From 14a480c3a3b7d5e34a471e03975eb808efbfcd72 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 11:44:50 +0300 Subject: [PATCH 03/16] doit: Fix typo in Fedora targets Fix a typo when building a Fedora target. Also, add Fedora 42 support. --- dodo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dodo.py b/dodo.py index ff8877d..872f89d 100644 --- a/dodo.py +++ b/dodo.py @@ -8,7 +8,7 @@ from doit.action import CmdAction ARCH = "arm64" if platform.machine() == "arm64" else "i686" VERSION = open("share/version.txt").read().strip() -FEDORA_VERSIONS = ["40", "41"] +FEDORA_VERSIONS = ["40", "41", "42"] DEBIAN_VERSIONS = ["bullseye", "jammy", "mantic", "noble", "trixie"] ### Global parameters @@ -124,7 +124,7 @@ def build_deb(cwd): def build_rpm(version, cwd, qubes=False): """Build an .rpm package on the requested Fedora distro.""" - return build_linux_pkg(distro="Fedora", version=version, cwd=cwd, qubes=qubes) + return build_linux_pkg(distro="fedora", version=version, cwd=cwd, qubes=qubes) ### Tasks From 092eec55d19416461ac9c41cd05b522d301cab58 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 11:45:47 +0300 Subject: [PATCH 04/16] doit: Remove unused 'DEBIAN_VERSIONS' variable --- dodo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dodo.py b/dodo.py index 872f89d..f38700f 100644 --- a/dodo.py +++ b/dodo.py @@ -9,7 +9,6 @@ from doit.action import CmdAction ARCH = "arm64" if platform.machine() == "arm64" else "i686" VERSION = open("share/version.txt").read().strip() FEDORA_VERSIONS = ["40", "41", "42"] -DEBIAN_VERSIONS = ["bullseye", "jammy", "mantic", "noble", "trixie"] ### Global parameters From e0b10c5e4057e08f8dee6870a4fd94b528f52eda Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 11:46:18 +0300 Subject: [PATCH 05/16] doit: Remove tessdata dir from targets Remove the tesseract data dir from the doit targets, else we encounter the following error: Traceback (most recent call last): [...] File "[...]/Library/Caches/pypoetry/virtualenvs/dangerzone-52Yr5wv_-py3.11/lib/python3.11/site-packages/doit/dependency.py", line 39, in get_file_md5 with open(path, 'rb') as file_data: ^^^^^^^^^^^^^^^^ IsADirectoryError: [Errno 21] Is a directory: 'share/tessdata' --- dodo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dodo.py b/dodo.py index f38700f..bd2b3a1 100644 --- a/dodo.py +++ b/dodo.py @@ -43,7 +43,6 @@ def list_language_data(): tessdata_dir = Path("share") / "tessdata" langs = json.loads(open(tessdata_dir.parent / "ocr-languages.json").read()).values() targets = [tessdata_dir / f"{lang}.traineddata" for lang in langs] - targets.append(tessdata_dir) return targets From 0b3bf89d5b328751da747eee199d575dd3d5c384 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 12:01:14 +0300 Subject: [PATCH 06/16] Implicitly run doit with `poetry run` Implicitly run `doit` with `poetry run`, else `poetry env remove --all` will remove the calling Python interpreter. --- Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 48347cc..38af94c 100644 --- a/Makefile +++ b/Makefile @@ -54,22 +54,21 @@ Dockerfile: Dockerfile.env Dockerfile.in poetry-install: poetry install - .PHONY: build-clean build-clean: - doit clean + poetry run doit clean .PHONY: build-macos-intel build-macos-intel: build-clean poetry-install - doit -n 8 + poetry run doit -n 8 .PHONY: build-macos-arm build-macos-arm: build-clean poetry-install - doit -n 8 macos_build_dmg + poetry run doit -n 8 macos_build_dmg .PHONY: build-linux build-linux: build-clean poetry-install - doit -n 8 fedora_rpm debian_deb + poetry run doit -n 8 fedora_rpm debian_deb .PHONY: regenerate-reference-pdfs regenerate-reference-pdfs: From c7121b69a398390bfa38df0afd8e73c038b1170b Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 12:11:03 +0300 Subject: [PATCH 07/16] Prefer `poetry sync` to `poetry install --sync` Use `poetry sync` instead of `poetry install --sync`, since the latter is deprecated and will be removed after June 2025, as seen in the following warning message: The `--sync` option is deprecated and slated for removal in the next minor release after June 2025, use the `poetry sync` command instead. --- RELEASE.md | 4 ++-- dev_scripts/qa.py | 2 +- dodo.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 5df7288..18700bb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -141,7 +141,7 @@ Here is what you need to do: poetry env remove --all # Install the dependencies - poetry install --sync + poetry sync ``` - [ ] Build the container image and the OCR language data @@ -223,7 +223,7 @@ The Windows release is performed in a Windows 11 virtual machine (as opposed to poetry env remove --all # Install the dependencies - poetry install --sync + poetry sync ``` - [ ] Copy the container image into the VM diff --git a/dev_scripts/qa.py b/dev_scripts/qa.py index b1ee7d9..e9d8575 100755 --- a/dev_scripts/qa.py +++ b/dev_scripts/qa.py @@ -836,7 +836,7 @@ class QAWindows(QABase): ) def install_poetry(self): self.run("python", "-m", "pip", "install", "poetry", "poetry-plugin-export") - self.run("poetry", "install", "--sync") + self.run("poetry", "sync") @QABase.task("Build Dangerzone container image", ref=REF_BUILD, auto=True) def build_image(self): diff --git a/dodo.py b/dodo.py index bd2b3a1..1a7eda3 100644 --- a/dodo.py +++ b/dodo.py @@ -206,7 +206,7 @@ def task_build_image(): def task_poetry_install(): """Setup the Poetry environment""" - return {"actions": ["poetry install --sync"], "clean": ["poetry env remove --all"]} + return {"actions": ["poetry sync"], "clean": ["poetry env remove --all"]} def task_macos_build_dmg(): From 33b2a183ce576718c798ce30077b1c796681ac37 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 12:14:08 +0300 Subject: [PATCH 08/16] docs: Improve doit docs --- docs/developer/doit.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/developer/doit.md b/docs/developer/doit.md index bf8fb16..dfbc0f5 100644 --- a/docs/developer/doit.md +++ b/docs/developer/doit.md @@ -42,7 +42,8 @@ doit ## Tips and tricks * You can run `doit list --all -s` to see the full list of tasks, their - dependencies, and whether they are up to date. + dependencies, and whether they are up to date (U) or will run (R). Note that + certain small tasks are always configured to run. * You can run `doit info ` to see which dependencies are missing. * You can pass the following environment variables to the script, in order to affect some global parameters: From 843e68cdf7e97e9903a992ff66a6772e1e307bb2 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 12:14:35 +0300 Subject: [PATCH 09/16] Handle the case of empty tesseract dirs during download --- install/common/download-tessdata.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install/common/download-tessdata.py b/install/common/download-tessdata.py index cf2b366..47c909e 100644 --- a/install/common/download-tessdata.py +++ b/install/common/download-tessdata.py @@ -51,6 +51,8 @@ def main(): if files == expected_files: logger.info("Skipping tessdata download, language data already exists") return + elif not files: + logger.info("Tesseract dir is empty, proceeding to download language data") else: logger.info(f"Found {tessdata_dir} but contents do not match") return 1 From 1a644e2506f7a30c04af7e6f52ca3940214f675f Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 14:34:46 +0300 Subject: [PATCH 10/16] Do not install poetry-plugin-export Do not unconditionally install the Poetry plugin for exporting dependencies as a requirements.txt file, since it's used only when building a Debian package. Keep it instead in the Linux instructions and when building a Dangerzone environment. --- BUILD.md | 6 +++--- RELEASE.md | 4 ++-- dev_scripts/qa.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/BUILD.md b/BUILD.md index 3ef6b63..2580f52 100644 --- a/BUILD.md +++ b/BUILD.md @@ -113,7 +113,7 @@ Install Poetry using `pipx`: ```sh pipx install poetry -pipx inject poetry poetry-plugin-export +pipx inject poetry ``` Clone this repository: @@ -326,7 +326,7 @@ cd dangerzone Install Python dependencies: ```sh -python3 -m pip install poetry poetry-plugin-export +python3 -m pip install poetry poetry install ``` @@ -387,7 +387,7 @@ Install Microsoft Visual C++ 14.0 or greater. Get it with ["Microsoft C++ Build Install [poetry](https://python-poetry.org/). Open PowerShell, and run: ``` -python -m pip install poetry poetry-plugin-export +python -m pip install poetry ``` Install git from [here](https://git-scm.com/download/win), open a Windows terminal (`cmd.exe`) and clone this repository: diff --git a/RELEASE.md b/RELEASE.md index 18700bb..17fbccb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -123,7 +123,7 @@ Here is what you need to do: # In case of a new Python installation or minor version upgrade, e.g., from # 3.11 to 3.12, reinstall Poetry - python3 -m pip install poetry poetry-plugin-export + python3 -m pip install poetry # You can verify the correct Python version is used poetry debug info @@ -205,7 +205,7 @@ The Windows release is performed in a Windows 11 virtual machine (as opposed to ```bash # In case of a new Python installation or minor version upgrade, e.g., from # 3.11 to 3.12, reinstall Poetry - python3 -m pip install poetry poetry-plugin-export + python3 -m pip install poetry # You can verify the correct Python version is used poetry debug info diff --git a/dev_scripts/qa.py b/dev_scripts/qa.py index e9d8575..86d33df 100755 --- a/dev_scripts/qa.py +++ b/dev_scripts/qa.py @@ -331,7 +331,7 @@ Install Poetry using `pipx`: ```sh pipx install poetry -pipx inject poetry poetry-plugin-export +pipx inject poetry ``` Clone this repository: @@ -397,7 +397,7 @@ Install Microsoft Visual C++ 14.0 or greater. Get it with ["Microsoft C++ Build Install [poetry](https://python-poetry.org/). Open PowerShell, and run: ``` -python -m pip install poetry poetry-plugin-export +python -m pip install poetry ``` Install git from [here](https://git-scm.com/download/win), open a Windows terminal (`cmd.exe`) and clone this repository: @@ -835,7 +835,7 @@ class QAWindows(QABase): "Install Poetry and the project's dependencies", ref=REF_BUILD, auto=True ) def install_poetry(self): - self.run("python", "-m", "pip", "install", "poetry", "poetry-plugin-export") + self.run("python", "-m", "pip", "install", "poetry") self.run("poetry", "sync") @QABase.task("Build Dangerzone container image", ref=REF_BUILD, auto=True) From 57667a96bea81006170ef7cf079b2258448fab1b Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 14:55:54 +0300 Subject: [PATCH 11/16] Add a way to unset the container runtime Add a way to set the container runtime that Dangerzone uses back to the default. --- dangerzone/cli.py | 20 +++++++++++++++----- dangerzone/settings.py | 4 ++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/dangerzone/cli.py b/dangerzone/cli.py index ca5b644..1353995 100644 --- a/dangerzone/cli.py +++ b/dangerzone/cli.py @@ -52,7 +52,11 @@ def print_header(s: str) -> None: @click.option( "--set-container-runtime", required=False, - help="The path to the container runtime you want to set in the settings", + help=( + "The name or full path of the container runtime you want Dangerzone to use." + " You can specify the value 'default' if you want to take back your choice, and" + " let Dangerzone use the default runtime for this OS" + ), ) @click.version_option(version=get_version(), message="%(version)s") @errors.handle_document_errors @@ -69,10 +73,16 @@ def cli_main( display_banner() if set_container_runtime: settings = Settings() - container_runtime = settings.set_custom_runtime( - set_container_runtime, autosave=True - ) - click.echo(f"Set the settings container_runtime to {container_runtime}") + if set_container_runtime == "default": + settings.unset_custom_runtime() + click.echo( + "Instructed Dangerzone to use the default container runtime for this OS" + ) + else: + container_runtime = settings.set_custom_runtime( + set_container_runtime, autosave=True + ) + click.echo(f"Set the settings container_runtime to {container_runtime}") sys.exit(0) elif not filenames: raise click.UsageError("Missing argument 'FILENAMES...'") diff --git a/dangerzone/settings.py b/dangerzone/settings.py index a95917b..0e30896 100644 --- a/dangerzone/settings.py +++ b/dangerzone/settings.py @@ -52,6 +52,10 @@ class Settings: self.save() return container_runtime + def unset_custom_runtime(self) -> None: + self.settings.pop("container_runtime") + self.save() + def get(self, key: str) -> Any: return self.settings[key] From 9d5761069343dad063a98dae4d346c47cc7a8975 Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 2 Apr 2025 15:04:24 +0300 Subject: [PATCH 12/16] IMPROVE_ME: Handle the case where Docker is not installed This looks like a regression, and we need to find a better way to handle it, or add a unit test. --- dangerzone/gui/main_window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 38b871f..0f9b79c 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -228,7 +228,9 @@ class MainWindow(QtWidgets.QMainWindow): if not is_version_valid: self.handle_docker_desktop_version_check(is_version_valid, version) except errors.UnsupportedContainerRuntime as e: - pass # It's catched later in the flow. + pass # It's caught later in the flow. + except errors.NoContainerTechException as e: + pass # It's caught later in the flow. self.show() From cb40518fbda378d6e0cded17facdd2021298050f Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Thu, 3 Apr 2025 20:18:32 +0300 Subject: [PATCH 13/16] Make our build-image.py script runable on Windows --- install/common/build-image.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/install/common/build-image.py b/install/common/build-image.py index 65a763d..868c5b1 100644 --- a/install/common/build-image.py +++ b/install/common/build-image.py @@ -5,7 +5,7 @@ import subprocess import sys from pathlib import Path -BUILD_CONTEXT = "dangerzone/" +BUILD_CONTEXT = "dangerzone" IMAGE_NAME = "dangerzone.rocks/dangerzone" if platform.system() in ["Darwin", "Windows"]: CONTAINER_RUNTIME = "docker" @@ -122,7 +122,8 @@ def main(): subprocess.run( [ - "./dev_scripts/repro-build.py", + sys.executable, + str(Path("dev_scripts") / "repro-build.py"), "build", "--runtime", args.runtime, From 41d5a4f2718a4c153cdda97d7d631c77a9b0260e Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Mon, 7 Apr 2025 13:48:52 +0300 Subject: [PATCH 14/16] Bypass a cx-freeze issue for fitz._wxcolors Bypass an issue with `cx-freeze` that fails to include the `fitz._wxcolors` module in the final Windows artifact. Refs #1128 --- setup-windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup-windows.py b/setup-windows.py index ac5331a..7252b58 100644 --- a/setup-windows.py +++ b/setup-windows.py @@ -13,7 +13,7 @@ setup( description="Dangerzone", options={ "build_exe": { - "packages": ["dangerzone", "dangerzone.gui"], + "packages": ["dangerzone", "dangerzone.gui", "pymupdf._wxcolors"], "excludes": ["test", "tkinter"], "include_files": [("share", "share"), ("LICENSE", "LICENSE")], "include_msvcr": True, From e84b7a3fbe71f20a111437996cf58d1346c3f9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Mon, 7 Apr 2025 14:58:59 +0200 Subject: [PATCH 15/16] Move multithreading patch up so that it's working in the GUI --- dangerzone/__init__.py | 6 ++++++ dangerzone/gui/main_window.py | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dangerzone/__init__.py b/dangerzone/__init__.py index f029398..a67ebcf 100644 --- a/dangerzone/__init__.py +++ b/dangerzone/__init__.py @@ -4,6 +4,12 @@ import sys logger = logging.getLogger(__name__) +# Call freeze_support() to avoid passing unknown options to the subprocess. +# See https://github.com/freedomofpress/dangerzone/issues/873 +import multiprocessing + +multiprocessing.freeze_support() + try: from . import vendor # type: ignore [attr-defined] diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 0f9b79c..fd86817 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -3,7 +3,6 @@ import os import platform import tempfile import typing -from multiprocessing import freeze_support from multiprocessing.pool import ThreadPool from pathlib import Path from typing import List, Optional @@ -1238,9 +1237,6 @@ class DocumentsListWidget(QtWidgets.QListWidget): def start_conversion(self) -> None: if not self.thread_pool_initized: max_jobs = self.dangerzone.isolation_provider.get_max_parallel_conversions() - # Call freeze_support() to avoid passing unknown options to the subprocess. - # See https://github.com/freedomofpress/dangerzone/issues/873 - freeze_support() self.thread_pool = ThreadPool(max_jobs) for doc in self.docs_list: From 019116361f9e0da2b34e9c5d8a222b6777a93e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Mon, 7 Apr 2025 15:49:46 +0200 Subject: [PATCH 16/16] Document the Makefile targets It now outputs the following: ``` build-linux Build linux packages (.rpm and .deb) build-macos-arm Build macOS Apple Silicon package (.dmg) build-macos-intel Build macOS intel package (.dmg) Dockerfile Regenerate the Dockerfile from its template fix apply all the suggestions from ruff help Print this message and exit. lint Check the code for linting, formatting, and typing issues with ruff and mypy regenerate-reference-pdfs Regenerate the reference PDFs test Run the tests test-large Run large test set ``` --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 38af94c..e4ae230 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ fix: ## apply all the suggestions from ruff ruff format .PHONY: test -test: +test: ## Run the tests # Make each GUI test run as a separate process, to avoid segfaults due to # shared state. # See more in https://github.com/freedomofpress/dangerzone/issues/493 @@ -47,11 +47,11 @@ test-large: test-large-init ## Run large test set python -m pytest --tb=no tests/test_large_set.py::TestLargeSet -v $(JUNIT_FLAGS) --junitxml=$(TEST_LARGE_RESULTS) python $(TEST_LARGE_RESULTS)/report.py $(TEST_LARGE_RESULTS) -Dockerfile: Dockerfile.env Dockerfile.in +Dockerfile: Dockerfile.env Dockerfile.in ## Regenerate the Dockerfile from its template poetry run jinja2 Dockerfile.in Dockerfile.env > Dockerfile .PHONY: poetry-install -poetry-install: +poetry-install: ## Install project dependencies poetry install .PHONY: build-clean @@ -59,19 +59,19 @@ build-clean: poetry run doit clean .PHONY: build-macos-intel -build-macos-intel: build-clean poetry-install +build-macos-intel: build-clean poetry-install ## Build macOS intel package (.dmg) poetry run doit -n 8 .PHONY: build-macos-arm -build-macos-arm: build-clean poetry-install +build-macos-arm: build-clean poetry-install ## Build macOS Apple Silicon package (.dmg) poetry run doit -n 8 macos_build_dmg .PHONY: build-linux -build-linux: build-clean poetry-install +build-linux: build-clean poetry-install ## Build linux packages (.rpm and .deb) poetry run doit -n 8 fedora_rpm debian_deb .PHONY: regenerate-reference-pdfs -regenerate-reference-pdfs: +regenerate-reference-pdfs: ## Regenerate the reference PDFs pytest tests/test_cli.py -k regenerate --generate-reference-pdfs # Makefile self-help borrowed from the securedrop-client project # Explaination of the below shell command should it ever break.