mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-10 15:31:50 +02:00
Compare commits
62 commits
9810ae402b
...
d66af442b3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d66af442b3 | ||
![]() |
eb345562da | ||
![]() |
d080d03f5a | ||
![]() |
767bfa7e48 | ||
![]() |
37ec91aae2 | ||
![]() |
cecfe63338 | ||
![]() |
4da6b92e12 | ||
![]() |
b06d1aebed | ||
![]() |
da5490a5a1 | ||
![]() |
e96b44e10a | ||
![]() |
7624624471 | ||
![]() |
fb7c2088e2 | ||
![]() |
1ea2f109cb | ||
![]() |
df3063a825 | ||
![]() |
57bb7286ef | ||
![]() |
fbe05065c9 | ||
![]() |
54ffc63c4f | ||
![]() |
bdc4cf13c4 | ||
![]() |
92d7bd6bee | ||
![]() |
7c5a191a5c | ||
![]() |
4bd794dbd1 | ||
![]() |
3eac00b873 | ||
![]() |
ec9f8835e0 | ||
![]() |
0383081394 | ||
![]() |
25fba42022 | ||
![]() |
e54567b7d4 | ||
![]() |
2a8355fb88 | ||
![]() |
e22c795cb7 | ||
![]() |
909560353d | ||
![]() |
6a5e76f2b4 | ||
![]() |
20152fac13 | ||
![]() |
6b51d56e9f | ||
![]() |
309bd12423 | ||
![]() |
1c0a99fcd2 | ||
![]() |
4b5f4b27d7 | ||
![]() |
f537d54ed2 | ||
![]() |
32641603ee | ||
![]() |
a915ae8442 | ||
![]() |
38a803085f | ||
![]() |
2053c98c09 | ||
![]() |
3db1ca1fbb | ||
![]() |
3fff16cc7e | ||
![]() |
8bd9c05832 | ||
![]() |
41e78c907f | ||
![]() |
265c1dde97 | ||
![]() |
ccb302462d | ||
![]() |
4eadc30605 | ||
![]() |
abb71e0fe5 | ||
![]() |
4638444290 | ||
![]() |
68da50a6b2 | ||
![]() |
cc5ba29455 | ||
![]() |
180b9442ab | ||
![]() |
f349e16523 | ||
![]() |
adddb1ecb7 | ||
![]() |
8e57d81a74 | ||
![]() |
3bcf5fc147 | ||
![]() |
60df4f7e35 | ||
![]() |
9fa3c80404 | ||
![]() |
4bf7f9cbb4 | ||
![]() |
fdc27c4d3b | ||
![]() |
23f5f96220 | ||
![]() |
5744215d99 |
39 changed files with 1960 additions and 1108 deletions
2
.github/ISSUE_TEMPLATE/bug_report_linux.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report_linux.yml
vendored
|
@ -36,7 +36,7 @@ body:
|
|||
attributes:
|
||||
label: Podman info
|
||||
description: |
|
||||
If the bug occurs during document conversion, or is otherwise related with Podman, please copy and paste the following commands in your terminal, and provide us with the output:
|
||||
Please copy and paste the following commands in your terminal, and provide us with the output:
|
||||
|
||||
```shell
|
||||
podman version
|
||||
|
|
3
.github/ISSUE_TEMPLATE/bug_report_macos.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report_macos.yml
vendored
|
@ -48,8 +48,7 @@ body:
|
|||
attributes:
|
||||
label: Docker info
|
||||
description: |
|
||||
If the bug occurs during document conversion, or is otherwise related
|
||||
with Docker, please copy and paste the following commands in your
|
||||
Please copy and paste the following commands in your
|
||||
terminal, and provide us with the output:
|
||||
|
||||
```shell
|
||||
|
|
|
@ -35,8 +35,7 @@ body:
|
|||
attributes:
|
||||
label: Docker info
|
||||
description: |
|
||||
If the bug occurs during document conversion, or is otherwise related
|
||||
with Docker, please copy and paste the following commands in your
|
||||
Please copy and paste the following commands in your
|
||||
terminal, and provide us with the output:
|
||||
|
||||
```shell
|
||||
|
|
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
@ -74,6 +74,8 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
|
@ -83,7 +85,7 @@ jobs:
|
|||
id: cache-container-image
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
path: |
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
@ -95,6 +97,7 @@ jobs:
|
|||
python3 ./install/common/build-image.py
|
||||
echo ${{ github.token }} | podman login ghcr.io -u USERNAME --password-stdin
|
||||
gunzip -c share/container.tar.gz | podman load
|
||||
tag=$(cat share/image-id.txt)
|
||||
podman push \
|
||||
dangerzone.rocks/dangerzone \
|
||||
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone
|
||||
dangerzone.rocks/dangerzone:$tag \
|
||||
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone:tag
|
||||
|
|
35
.github/workflows/check_pr.yml
vendored
Normal file
35
.github/workflows/check_pr.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Check branch conformity
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
prevent-fixup-commits:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
target: debian-bookworm
|
||||
distro: debian
|
||||
version: bookworm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: prevent fixup commits
|
||||
run: |
|
||||
git fetch origin
|
||||
git status
|
||||
git log --pretty=format:%s origin/main..HEAD | grep -ie '^fixup\|^wip' && exit 1 || true
|
||||
|
||||
check-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: ensure CHANGELOG.md is populated
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
if git diff --exit-code "origin/${BASE_REF}" -- CHANGELOG.md; then
|
||||
echo "::warning::No CHANGELOG.md modifications were found in this pull request."
|
||||
fi
|
19
.github/workflows/check_push.yml
vendored
19
.github/workflows/check_push.yml
vendored
|
@ -1,19 +0,0 @@
|
|||
name: Check branch conformity
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
prevent-fixup-commits:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
target: debian-bookworm
|
||||
distro: debian
|
||||
version: bookworm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: prevent fixup commits
|
||||
run: |
|
||||
git fetch origin
|
||||
git status
|
||||
git log --pretty=format:%s origin/main..HEAD | grep -ie '^fixup\|^wip' && exit 1 || true
|
60
.github/workflows/ci.yml
vendored
60
.github/workflows/ci.yml
vendored
|
@ -48,6 +48,8 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
|
@ -57,7 +59,7 @@ jobs:
|
|||
id: cache-container-image
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
path: |-
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
@ -68,6 +70,12 @@ jobs:
|
|||
sudo apt-get install -y python3-poetry
|
||||
python3 ./install/common/build-image.py
|
||||
|
||||
- name: Upload container image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: container.tar.gz
|
||||
path: share/container.tar.gz
|
||||
|
||||
download-tessdata:
|
||||
name: Download and cache Tesseract data
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -113,13 +121,24 @@ jobs:
|
|||
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
|
||||
- name: Run CLI tests
|
||||
run: poetry run make test
|
||||
# Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829
|
||||
- name: Set path for candle and light
|
||||
run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- name: Set up .NET CLI environment
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.x"
|
||||
- name: Install WiX Toolset
|
||||
run: dotnet tool install --global wix
|
||||
- name: Add WiX UI extension
|
||||
run: wix extension add --global WixToolset.UI.wixext
|
||||
- name: Build the MSI installer
|
||||
# NOTE: This also builds the .exe internally.
|
||||
run: poetry run .\install\windows\build-app.bat
|
||||
- name: Upload MSI installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Dangerzone.msi
|
||||
path: "dist/Dangerzone.msi"
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
macOS:
|
||||
name: "macOS (${{ matrix.arch }})"
|
||||
|
@ -151,7 +170,15 @@ jobs:
|
|||
- run: poetry install
|
||||
- name: Run CLI tests
|
||||
run: poetry run make test
|
||||
|
||||
- name: Build macOS app
|
||||
run: poetry run python ./install/macos/build-app.py
|
||||
- name: Upload macOS app
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Dangerzone-${{ matrix.arch }}.app
|
||||
path: "dist/Dangerzone.app"
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
build-deb:
|
||||
needs:
|
||||
- build-container-image
|
||||
|
@ -200,7 +227,7 @@ jobs:
|
|||
- name: Restore container cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
path: |-
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
@ -216,7 +243,7 @@ jobs:
|
|||
if: matrix.distro == 'debian' && matrix.version == 'bookworm'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dangerzone-${{ matrix.distro }}-${{ matrix.version }}.deb
|
||||
name: dangerzone.deb
|
||||
path: "deb_dist/dangerzone_*_*.deb"
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
@ -224,7 +251,7 @@ jobs:
|
|||
install-deb:
|
||||
name: "install-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
needs:
|
||||
- build-deb
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -255,7 +282,7 @@ jobs:
|
|||
- name: Download Dangerzone .deb
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dangerzone-debian-bookworm.deb
|
||||
name: dangerzone.deb
|
||||
path: "deb_dist/"
|
||||
|
||||
- name: Build end-user environment
|
||||
|
@ -307,7 +334,7 @@ jobs:
|
|||
- name: Restore container image
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
path: |-
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
@ -318,6 +345,14 @@ jobs:
|
|||
./dev_scripts/env.py --distro ${{ matrix.distro }} --version ${{ matrix.version }} \
|
||||
run --dev --no-gui ./dangerzone/install/linux/build-rpm.py
|
||||
|
||||
- name: Upload Dangerzone .rpm
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dangerzone-${{ matrix.distro }}-${{ matrix.version }}.rpm
|
||||
path: "dist/dangerzone-*.x86_64.rpm"
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
# Reclaim some space in this step, now that the dev environment is no
|
||||
# longer necessary. Previously, we encountered out-of-space issues while
|
||||
# running this CI job.
|
||||
|
@ -394,7 +429,7 @@ jobs:
|
|||
- name: Restore container image
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
|
||||
path: |-
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
@ -410,6 +445,7 @@ jobs:
|
|||
|
||||
- name: Setup xvfb (Linux)
|
||||
run: |
|
||||
sudo apt update
|
||||
# Stuff copied wildly from several stackoverflow posts
|
||||
sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev
|
||||
|
||||
|
|
19
.github/workflows/scan.yml
vendored
19
.github/workflows/scan.yml
vendored
|
@ -14,17 +14,24 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install container build dependencies
|
||||
run: sudo apt install pipx && pipx install poetry
|
||||
- name: Build container image
|
||||
run: python3 ./install/common/build-image.py --runtime docker --no-save
|
||||
- name: Get image tag
|
||||
id: tag
|
||||
run: |
|
||||
tag=$(docker images dangerzone.rocks/dangerzone --format '{{ .Tag }}')
|
||||
echo "tag=$tag" >> $GITHUB_OUTPUT
|
||||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||
# report.
|
||||
- name: Scan container image (no fail)
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
id: scan_container
|
||||
with:
|
||||
image: "dangerzone.rocks/dangerzone:latest"
|
||||
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
|
||||
fail-build: false
|
||||
only-fixed: false
|
||||
severity-cutoff: critical
|
||||
|
@ -36,9 +43,9 @@ jobs:
|
|||
- name: Inspect container scan report
|
||||
run: cat ${{ steps.scan_container.outputs.sarif }}
|
||||
- name: Scan container image
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
with:
|
||||
image: "dangerzone.rocks/dangerzone:latest"
|
||||
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
|
||||
fail-build: true
|
||||
only-fixed: false
|
||||
severity-cutoff: critical
|
||||
|
@ -51,7 +58,7 @@ jobs:
|
|||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||
# report.
|
||||
- name: Scan application (no fail)
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
id: scan_app
|
||||
with:
|
||||
path: "."
|
||||
|
@ -66,7 +73,7 @@ jobs:
|
|||
- name: Inspect application scan report
|
||||
run: cat ${{ steps.scan_app.outputs.sarif }}
|
||||
- name: Scan application
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
with:
|
||||
path: "."
|
||||
fail-build: true
|
||||
|
|
17
.github/workflows/scan_released.yml
vendored
17
.github/workflows/scan_released.yml
vendored
|
@ -24,13 +24,18 @@ jobs:
|
|||
CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar.gz
|
||||
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
|
||||
docker load -i ${CONTAINER_FILENAME}
|
||||
- name: Get image tag
|
||||
id: tag
|
||||
run: |
|
||||
tag=$(docker images dangerzone.rocks/dangerzone --format '{{ .Tag }}')
|
||||
echo "tag=$tag" >> $GITHUB_OUTPUT
|
||||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||
# report.
|
||||
- name: Scan container image (no fail)
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
id: scan_container
|
||||
with:
|
||||
image: "dangerzone.rocks/dangerzone:latest"
|
||||
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
|
||||
fail-build: false
|
||||
only-fixed: false
|
||||
severity-cutoff: critical
|
||||
|
@ -42,9 +47,9 @@ jobs:
|
|||
- name: Inspect container scan report
|
||||
run: cat ${{ steps.scan_container.outputs.sarif }}
|
||||
- name: Scan container image
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
with:
|
||||
image: "dangerzone.rocks/dangerzone:latest"
|
||||
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
|
||||
fail-build: true
|
||||
only-fixed: false
|
||||
severity-cutoff: critical
|
||||
|
@ -63,7 +68,7 @@ jobs:
|
|||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||
# report.
|
||||
- name: Scan application (no fail)
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
id: scan_app
|
||||
with:
|
||||
path: "."
|
||||
|
@ -78,7 +83,7 @@ jobs:
|
|||
- name: Inspect application scan report
|
||||
run: cat ${{ steps.scan_app.outputs.sarif }}
|
||||
- name: Scan application
|
||||
uses: anchore/scan-action@v5
|
||||
uses: anchore/scan-action@v6
|
||||
with:
|
||||
path: "."
|
||||
fail-build: true
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -149,3 +149,4 @@ share/container.tar
|
|||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
container/container-pip-requirements.txt
|
||||
.doit.db.db
|
||||
|
|
47
.grype.yaml
47
.grype.yaml
|
@ -2,47 +2,10 @@
|
|||
# latest release of Dangerzone, and offer our analysis.
|
||||
|
||||
ignore:
|
||||
- vulnerability: CVE-2024-5535
|
||||
# CVE-2024-5171
|
||||
# =============
|
||||
#
|
||||
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-5171
|
||||
# Verdict: Dangerzone is not affected. The rationale is the following:
|
||||
#
|
||||
# The affected library, `libaom.so`, is linked by GStreamer's `libgstaom.so`
|
||||
# library. The vulnerable `aom_img_alloc` function is only used when
|
||||
# **encoding** a video to AV1. LibreOffce uses the **decode** path instead,
|
||||
# when generating thumbnails.
|
||||
#
|
||||
# See also: https://github.com/freedomofpress/dangerzone/issues/895
|
||||
- vulnerability: CVE-2024-5171
|
||||
|
||||
# CVE-2024-45491, CVE-2024-45492
|
||||
# ===============================
|
||||
#
|
||||
# NVD Entries:
|
||||
# * https://nvd.nist.gov/vuln/detail/CVE-2024-45491
|
||||
# * https://nvd.nist.gov/vuln/detail/CVE-2024-45492
|
||||
#
|
||||
# Verdict: Dangerzone is not affected. The rationale is the following:
|
||||
#
|
||||
# The vulnerabilities that have been assigned to these CVEs affect only 32-bit
|
||||
# architectures. Dangerzone ships only 64-bit images to users.
|
||||
#
|
||||
# See also: https://github.com/freedomofpress/dangerzone/issues/913
|
||||
- vulnerability: CVE-2024-45491
|
||||
- vulnerability: CVE-2024-45492
|
||||
|
||||
# CVE-2024-45490
|
||||
# CVE-2024-11053
|
||||
# ==============
|
||||
#
|
||||
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-45490
|
||||
# Verdict: Dangerzone is not affected. The rationale is the following:
|
||||
#
|
||||
# In order to exploit this bug, the caller must pass a negative length to the
|
||||
# `XML_ParseBuffer` function. This function is not directly used by
|
||||
# LibreOffice, which instead uses a higher-level wrapper. Therefore, our
|
||||
# understanding is that this path cannot be exploited by attackers.
|
||||
#
|
||||
# See also: https://github.com/freedomofpress/dangerzone/issues/913
|
||||
- vulnerability: CVE-2024-45490
|
||||
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-11053
|
||||
# Verdict: Dangerzone is not affected because libcurl is an HTTP client, and
|
||||
# the Dangerzone container does not make any network calls.
|
||||
- vulnerability: CVE-2024-11053
|
||||
|
|
21
BUILD.md
21
BUILD.md
|
@ -471,11 +471,24 @@ poetry shell
|
|||
.\dev_scripts\dangerzone.bat
|
||||
```
|
||||
|
||||
### If you want to build the installer
|
||||
### If you want to build the Windows installer
|
||||
|
||||
* Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`.
|
||||
* Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix314.exe`.
|
||||
* Add `C:\Program Files (x86)\WiX Toolset v3.14\bin` to the path ([instructions](https://web.archive.org/web/20230221104142/https://windowsloop.com/how-to-add-to-windows-path/)).
|
||||
Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) version 6 or later. Then, open a terminal and install the latest version of [WiX Toolset .NET tool](https://wixtoolset.org/) **v5** with:
|
||||
|
||||
```sh
|
||||
dotnet tool install --global wix --version 5.*
|
||||
```
|
||||
|
||||
Install the WiX UI extension. You may need to open a new terminal in order to use the newly installed `wix` .NET tool:
|
||||
|
||||
```sh
|
||||
wix extension add --global WixToolset.UI.wixext/5.x.y
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> To avoid compatibility issues, ensure the WiX UI extension version matches the version of the WiX Toolset.
|
||||
>
|
||||
> Run `wix --version` to check the version of WiX Toolset you have installed and replace `5.x.y` with the full version number without the Git revision.
|
||||
|
||||
### If you want to sign binaries with Authenticode
|
||||
|
||||
|
|
|
@ -16,6 +16,11 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
|
|||
|
||||
- Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999))
|
||||
|
||||
### Development changes
|
||||
|
||||
Thanks [@jkarasti](https://github.com/jkarasti) for the contribution.
|
||||
- Automate a large portion of our release tasks with `doit` ([#1016](https://github.com/freedomofpress/dangerzone/issues/1016))
|
||||
|
||||
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
|
||||
|
||||
### Added
|
||||
|
|
51
Makefile
51
Makefile
|
@ -1,23 +1,6 @@
|
|||
LARGE_TEST_REPO_DIR:=tests/test_docs_large
|
||||
GIT_DESC=$$(git describe)
|
||||
JUNIT_FLAGS := --capture=sys -o junit_logging=all
|
||||
|
||||
.PHONY: lint-black
|
||||
lint-black: ## check python source code formatting issues, with black
|
||||
black --check --diff ./
|
||||
|
||||
.PHONY: lint-black-apply
|
||||
lint-black-apply: ## apply black's source code formatting suggestions
|
||||
black ./
|
||||
|
||||
.PHONY: lint-isort
|
||||
lint-isort: ## check imports are organized, with isort
|
||||
isort --check --diff ./
|
||||
|
||||
.PHONY: lint-isort-apply
|
||||
lint-isort-apply: ## apply isort's imports organization suggestions
|
||||
isort ./
|
||||
|
||||
MYPY_ARGS := --ignore-missing-imports \
|
||||
--disallow-incomplete-defs \
|
||||
--disallow-untyped-defs \
|
||||
|
@ -26,19 +9,17 @@ MYPY_ARGS := --ignore-missing-imports \
|
|||
--warn-unused-ignores \
|
||||
--exclude $(LARGE_TEST_REPO_DIR)/*.py
|
||||
|
||||
mypy-host:
|
||||
.PHONY: lint
|
||||
lint: ## Check the code for linting, formatting, and typing issues with ruff and mypy
|
||||
ruff check
|
||||
ruff format --check
|
||||
mypy $(MYPY_ARGS) dangerzone
|
||||
|
||||
mypy-tests:
|
||||
mypy $(MYPY_ARGS) tests
|
||||
|
||||
mypy: mypy-host mypy-tests ## check type hints with mypy
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-black lint-isort mypy ## check the code with various linters
|
||||
|
||||
.PHONY: lint-apply
|
||||
format: lint-black-apply lint-isort-apply ## apply all the linter's suggestions
|
||||
.PHONY: fix
|
||||
fix: ## apply all the suggestions from ruff
|
||||
ruff check --fix
|
||||
ruff format
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
@ -66,6 +47,22 @@ 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)
|
||||
|
||||
.PHONY: build-clean
|
||||
build-clean:
|
||||
doit clean
|
||||
|
||||
.PHONY: build-macos-intel
|
||||
build-macos-intel: build-clean
|
||||
doit -n 8
|
||||
|
||||
.PHONY: build-macos-arm
|
||||
build-macos-arm: build-clean
|
||||
doit -n 8 macos_build_dmg
|
||||
|
||||
.PHONY: build-linux
|
||||
build-linux: build-clean
|
||||
doit -n 8 fedora_rpm debian_deb
|
||||
|
||||
# Makefile self-help borrowed from the securedrop-client project
|
||||
# Explaination of the below shell command should it ever break.
|
||||
# 1. Set the field separator to ": ##" and any make targets that might appear between : and ##
|
||||
|
|
197
QA.md
Normal file
197
QA.md
Normal file
|
@ -0,0 +1,197 @@
|
|||
## QA
|
||||
|
||||
To ensure that new releases do not introduce regressions, and support existing
|
||||
and newer platforms, we have to test that the produced packages work as expected.
|
||||
|
||||
Check the following:
|
||||
|
||||
- [ ] Make sure that the tip of the `main` branch passes the CI tests.
|
||||
- [ ] Make sure that the Apple account has a valid application password and has
|
||||
agreed to the latest Apple terms (see [macOS release](#macos-release)
|
||||
section).
|
||||
|
||||
Because it is repetitive, we wrote a script to help with the QA.
|
||||
It can run the tasks for you, pausing when it needs manual intervention.
|
||||
|
||||
You can run it with a command like:
|
||||
|
||||
```bash
|
||||
poetry run ./dev_scripts/qa.py {distro}-{version}
|
||||
```
|
||||
|
||||
### The checklist
|
||||
|
||||
- [ ] Create a test build in Windows and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Build and run the Dangerzone .exe
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in macOS (Intel CPU) and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in macOS (M1/2 CPU) and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Ubuntu LTS platform (Ubuntu 24.04
|
||||
as of writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create a .deb package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Fedora platform (Fedora 41 as of
|
||||
writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create an .rpm package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Qubes Fedora template (Fedora 40 as
|
||||
of writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create a Qubes .rpm package and install it system-wide.
|
||||
- [ ] Ensure that the Dangerzone application appears in the "Applications"
|
||||
tab.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below) and make sure
|
||||
they spawn disposable qubes.
|
||||
|
||||
### Scenarios
|
||||
|
||||
#### 1. Dangerzone correctly identifies that Docker/Podman is not installed
|
||||
|
||||
_(Only for MacOS / Windows)_
|
||||
|
||||
Temporarily hide the Docker/Podman binaries, e.g., rename the `docker` /
|
||||
`podman` binaries to something else. Then run Dangerzone. Dangerzone should
|
||||
prompt the user to install Docker/Podman.
|
||||
|
||||
#### 2. Dangerzone correctly identifies that Docker is not running
|
||||
|
||||
_(Only for MacOS / Windows)_
|
||||
|
||||
Stop the Docker Desktop application. Then run Dangerzone. Dangerzone should
|
||||
prompt the user to start Docker Desktop.
|
||||
|
||||
|
||||
#### 3. Updating Dangerzone handles external state correctly.
|
||||
|
||||
_(Applies to Windows/MacOS)_
|
||||
|
||||
Install the previous version of Dangerzone, downloaded from the website.
|
||||
|
||||
Open the Dangerzone application and enable some non-default settings.
|
||||
**If there are new settings, make sure to change those as well**.
|
||||
|
||||
Close the Dangerzone application and get the container image for that
|
||||
version. For example:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone <tag> <image ID> <date> <size>
|
||||
```
|
||||
|
||||
Then run the version under QA and ensure that the settings remain changed.
|
||||
|
||||
Afterwards check that new docker image was installed by running the same command
|
||||
and seeing the following differences:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone <other tag> <different ID> <newer date> <different size>
|
||||
```
|
||||
|
||||
#### 4. Dangerzone successfully installs the container image
|
||||
|
||||
_(Only for Linux)_
|
||||
|
||||
Remove the Dangerzone container image from Docker/Podman. Then run Dangerzone.
|
||||
Dangerzone should install the container image successfully.
|
||||
|
||||
#### 5. Dangerzone retains the settings of previous runs
|
||||
|
||||
Run Dangerzone and make some changes in the settings (e.g., change the OCR
|
||||
language, toggle whether to open the document after conversion, etc.). Restart
|
||||
Dangerzone. Dangerzone should show the settings that the user chose.
|
||||
|
||||
#### 6. Dangerzone reports failed conversions
|
||||
|
||||
Run Dangerzone and convert the `tests/test_docs/sample_bad_pdf.pdf` document.
|
||||
Dangerzone should fail gracefully, by reporting that the operation failed, and
|
||||
showing the following error message:
|
||||
|
||||
> The document format is not supported
|
||||
|
||||
#### 7. Dangerzone succeeds in converting multiple documents
|
||||
|
||||
Run Dangerzone against a list of documents, and tick all options. Ensure that:
|
||||
* Conversions take place sequentially.
|
||||
* Attempting to close the window while converting asks the user if they want to
|
||||
abort the conversions.
|
||||
* Conversions are completed successfully.
|
||||
* Conversions show individual progress in real-time (double-check for Qubes).
|
||||
* _(Only for Linux)_ The resulting files open with the PDF viewer of our choice.
|
||||
* OCR seems to have detected characters in the PDF files.
|
||||
* The resulting files have been saved with the proper suffix, in the proper
|
||||
location.
|
||||
* The original files have been saved in the `unsafe/` directory.
|
||||
|
||||
#### 8. Dangerzone is able to handle drag-n-drop
|
||||
|
||||
Run Dangerzone against a set of documents that you drag-n-drop. Files should be
|
||||
added and conversion should run without issue.
|
||||
|
||||
> [!TIP]
|
||||
> On our end-user container environments for Linux, we can start a file manager
|
||||
> with `thunar &`.
|
||||
|
||||
#### 9. Dangerzone CLI succeeds in converting multiple documents
|
||||
|
||||
_(Only for Windows and Linux)_
|
||||
|
||||
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
||||
sequentially, are completed successfully, and we see their progress.
|
||||
|
||||
#### 10. Dangerzone can open a document for conversion via right-click -> "Open With"
|
||||
|
||||
_(Only for Windows, MacOS and Qubes)_
|
||||
|
||||
Go to a directory with office documents, right-click on one, and click on "Open
|
||||
With". We should be able to open the file with Dangerzone, and then convert it.
|
||||
|
||||
#### 11. Dangerzone shows helpful errors for setup issues on Qubes
|
||||
|
||||
_(Only for Qubes)_
|
||||
|
||||
Check what errors does Dangerzone throw in the following scenarios. The errors
|
||||
should point the user to the Qubes notifications in the top-right corner:
|
||||
|
||||
1. The `dz-dvm` template does not exist. We can trigger this scenario by
|
||||
temporarily renaming this template.
|
||||
2. The Dangerzone RPC policy does not exist. We can trigger this scenario by
|
||||
temporarily renaming the `dz.Convert` policy.
|
||||
3. The `dz-dvm` disposable Qube cannot start due to insufficient resources. We
|
||||
can trigger this scenario by temporarily increasing the minimum required RAM
|
||||
of the `dz-dvm` template to more than the available amount.
|
415
RELEASE.md
415
RELEASE.md
|
@ -1,12 +1,13 @@
|
|||
# Release instructions
|
||||
|
||||
This section documents the release process. Unless you're a dangerzone developer making a release, you'll probably never need to follow it.
|
||||
This section documents how we currently release Dangerzone for the different distributions we support.
|
||||
|
||||
## Pre-release
|
||||
|
||||
Before making a release, all of these should be complete:
|
||||
Here is a list of tasks that should be done before issuing the release:
|
||||
|
||||
- [ ] Copy the checkboxes from these instructions onto a new issue and call it **QA and Release version \<VERSION\>**
|
||||
- [ ] Create a new issue named **QA and Release for version \<VERSION\>**, to track the general progress.
|
||||
You can generate its content with the the `poetry run ./dev_scripts/generate-release-tasks.py` command.
|
||||
- [ ] [Add new Linux platforms and remove obsolete ones](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#add-new-platforms-and-remove-obsolete-ones)
|
||||
- [ ] Bump the Python dependencies using `poetry lock`
|
||||
- [ ] Update `version` in `pyproject.toml`
|
||||
|
@ -15,6 +16,8 @@ Before making a release, all of these should be complete:
|
|||
- [ ] Bump the Debian version by adding a new changelog entry in `debian/changelog`
|
||||
- [ ] Update screenshot in `README.md`, if necessary
|
||||
- [ ] CHANGELOG.md should be updated to include a list of all major changes since the last release
|
||||
- [ ] A draft release should be created. Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
|
||||
- [ ] Do the QA tasks
|
||||
|
||||
## Add new Linux platforms and remove obsolete ones
|
||||
|
||||
|
@ -37,7 +40,7 @@ In case of a new version (beta, RC, or official release):
|
|||
`BUILD.md` files where necessary.
|
||||
4. Send a PR with the above changes.
|
||||
|
||||
In case of an EOL version:
|
||||
In case of the removal of a version:
|
||||
|
||||
1. Remove any mention to this version from our repo.
|
||||
* Consult the previous paragraph, but also `grep` your way around.
|
||||
|
@ -51,192 +54,13 @@ Follow the instructions in `docs/developer/TESTING.md` to run the tests.
|
|||
|
||||
These tests will identify any regressions or progression in terms of document coverage.
|
||||
|
||||
## QA
|
||||
|
||||
To ensure that new releases do not introduce regressions, and support existing
|
||||
and newer platforms, we have to do the following:
|
||||
|
||||
- [ ] Make sure that the tip of the `main` branch passes the CI tests.
|
||||
- [ ] Make sure that the Apple account has a valid application password and has
|
||||
agreed to the latest Apple terms (see [macOS release](#macos-release)
|
||||
section).
|
||||
- [ ] Create a test build in Windows and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Build and run the Dangerzone .exe
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in macOS (Intel CPU) and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in macOS (M1/2 CPU) and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Ubuntu LTS platform (Ubuntu 24.04
|
||||
as of writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create a .deb package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Fedora platform (Fedora 41 as of
|
||||
writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create an .rpm package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
- [ ] Create a test build in the most recent Qubes Fedora template (Fedora 40 as
|
||||
of writing this) and make sure it works:
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create a Qubes .rpm package and install it system-wide.
|
||||
- [ ] Ensure that the Dangerzone application appears in the "Applications"
|
||||
tab.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below) and make sure
|
||||
they spawn disposable qubes.
|
||||
|
||||
### Scenarios
|
||||
|
||||
#### 1. Dangerzone correctly identifies that Docker/Podman is not installed
|
||||
|
||||
_(Only for MacOS / Windows)_
|
||||
|
||||
Temporarily hide the Docker/Podman binaries, e.g., rename the `docker` /
|
||||
`podman` binaries to something else. Then run Dangerzone. Dangerzone should
|
||||
prompt the user to install Docker/Podman.
|
||||
|
||||
#### 2. Dangerzone correctly identifies that Docker is not running
|
||||
|
||||
_(Only for MacOS / Windows)_
|
||||
|
||||
Stop the Docker Desktop application. Then run Dangerzone. Dangerzone should
|
||||
prompt the user to start Docker Desktop.
|
||||
|
||||
|
||||
#### 3. Updating Dangerzone handles external state correctly.
|
||||
|
||||
_(Applies to Windows/MacOS)_
|
||||
|
||||
Install the previous version of Dangerzone, downloaded from the website.
|
||||
|
||||
Open the Dangerzone application and enable some non-default settings.
|
||||
**If there are new settings, make sure to change those as well**.
|
||||
|
||||
Close the Dangerzone application and get the container image for that
|
||||
version. For example:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone:latest
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone latest <image ID> <date> <size>
|
||||
```
|
||||
|
||||
Then run the version under QA and ensure that the settings remain changed.
|
||||
|
||||
Afterwards check that new docker image was installed by running the same command
|
||||
and seeing the following differences:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone:latest
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone latest <different ID> <newer date> <different size>
|
||||
```
|
||||
|
||||
#### 4. Dangerzone successfully installs the container image
|
||||
|
||||
_(Only for Linux)_
|
||||
|
||||
Remove the Dangerzone container image from Docker/Podman. Then run Dangerzone.
|
||||
Dangerzone should install the container image successfully.
|
||||
|
||||
#### 5. Dangerzone retains the settings of previous runs
|
||||
|
||||
Run Dangerzone and make some changes in the settings (e.g., change the OCR
|
||||
language, toggle whether to open the document after conversion, etc.). Restart
|
||||
Dangerzone. Dangerzone should show the settings that the user chose.
|
||||
|
||||
#### 6. Dangerzone reports failed conversions
|
||||
|
||||
Run Dangerzone and convert the `tests/test_docs/sample_bad_pdf.pdf` document.
|
||||
Dangerzone should fail gracefully, by reporting that the operation failed, and
|
||||
showing the following error message:
|
||||
|
||||
> The document format is not supported
|
||||
|
||||
#### 7. Dangerzone succeeds in converting multiple documents
|
||||
|
||||
Run Dangerzone against a list of documents, and tick all options. Ensure that:
|
||||
* Conversions take place sequentially.
|
||||
* Attempting to close the window while converting asks the user if they want to
|
||||
abort the conversions.
|
||||
* Conversions are completed successfully.
|
||||
* Conversions show individual progress in real-time (double-check for Qubes).
|
||||
* _(Only for Linux)_ The resulting files open with the PDF viewer of our choice.
|
||||
* OCR seems to have detected characters in the PDF files.
|
||||
* The resulting files have been saved with the proper suffix, in the proper
|
||||
location.
|
||||
* The original files have been saved in the `unsafe/` directory.
|
||||
|
||||
#### 8. Dangerzone is able to handle drag-n-drop
|
||||
|
||||
Run Dangerzone against a set of documents that you drag-n-drop. Files should be
|
||||
added and conversion should run without issue.
|
||||
|
||||
> [!TIP]
|
||||
> On our end-user container environments for Linux, we can start a file manager
|
||||
> with `thunar &`.
|
||||
|
||||
#### 9. Dangerzone CLI succeeds in converting multiple documents
|
||||
|
||||
_(Only for Windows and Linux)_
|
||||
|
||||
Run Dangerzone CLI against a list of documents. Ensure that conversions happen
|
||||
sequentially, are completed successfully, and we see their progress.
|
||||
|
||||
#### 10. Dangerzone can open a document for conversion via right-click -> "Open With"
|
||||
|
||||
_(Only for Windows, MacOS and Qubes)_
|
||||
|
||||
Go to a directory with office documents, right-click on one, and click on "Open
|
||||
With". We should be able to open the file with Dangerzone, and then convert it.
|
||||
|
||||
#### 11. Dangerzone shows helpful errors for setup issues on Qubes
|
||||
|
||||
_(Only for Qubes)_
|
||||
|
||||
Check what errors does Dangerzone throw in the following scenarios. The errors
|
||||
should point the user to the Qubes notifications in the top-right corner:
|
||||
|
||||
1. The `dz-dvm` template does not exist. We can trigger this scenario by
|
||||
temporarily renaming this template.
|
||||
2. The Dangerzone RPC policy does not exist. We can trigger this scenario by
|
||||
temporarily renaming the `dz.Convert` policy.
|
||||
3. The `dz-dvm` disposable Qube cannot start due to insufficient resources. We
|
||||
can trigger this scenario by temporarily increasing the minimum required RAM
|
||||
of the `dz-dvm` template to more than the available amount.
|
||||
|
||||
## Release
|
||||
|
||||
Once we are confident that the release will be out shortly, and doesn't need any more changes:
|
||||
|
||||
- [ ] Create a PGP-signed git tag for the version, e.g., for dangerzone `v0.1.0`:
|
||||
|
||||
```
|
||||
```bash
|
||||
git tag -s v0.1.0
|
||||
git push origin v0.1.0
|
||||
```
|
||||
|
@ -252,6 +76,17 @@ Once we are confident that the release will be out shortly, and doesn't need any
|
|||
|
||||
### macOS Release
|
||||
|
||||
> [!TIP]
|
||||
> You can automate these steps from your macOS terminal app with:
|
||||
>
|
||||
> ```
|
||||
> export APPLE_ID=<email>
|
||||
> make build-macos-intel # for Intel macOS
|
||||
> make build-macos-arm # for Apple Silicon macOS
|
||||
> ```
|
||||
|
||||
The following needs to happen for both Silicon and Intel chipsets.
|
||||
|
||||
#### Initial Setup
|
||||
|
||||
- Build machine must have:
|
||||
|
@ -266,48 +101,83 @@ Once we are confident that the release will be out shortly, and doesn't need any
|
|||
|
||||
#### Releasing and Signing
|
||||
|
||||
Here is what you need to do:
|
||||
|
||||
- [ ] Verify and install the latest supported Python version from
|
||||
[python.org](https://www.python.org/downloads/macos/) (do not use the one from
|
||||
brew as it is known to [cause issues](https://github.com/freedomofpress/dangerzone/issues/471))
|
||||
* In case of a new Python installation or minor version upgrade, e.g., from
|
||||
3.11 to 3.12 , reinstall Poetry with `python3 -m pip install poetry`
|
||||
* You can verify the correct Python version is used with `poetry debug info`
|
||||
- [ ] Verify and checkout the git tag for this release
|
||||
- [ ] Run `poetry install --sync`
|
||||
- [ ] On the silicon mac, build the container image:
|
||||
|
||||
- [ ] Checkout the dependencies, and clean your local copy:
|
||||
|
||||
```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
|
||||
|
||||
# You can verify the correct Python version is used
|
||||
poetry debug info
|
||||
|
||||
# Replace with the actual version
|
||||
export DZ_VERSION=$(cat share/version.txt)
|
||||
|
||||
# Verify and checkout the git tag for this release:
|
||||
git checkout -f v$VERSION
|
||||
|
||||
# Clean the git repository
|
||||
git clean -df
|
||||
|
||||
# Clean up the environment
|
||||
poetry env remove --all
|
||||
|
||||
# Install the dependencies
|
||||
poetry install --sync
|
||||
```
|
||||
python3 ./install/common/build-image.py
|
||||
|
||||
- [ ] Build the container image and the OCR language data
|
||||
|
||||
```bash
|
||||
poetry run ./install/common/build-image.py
|
||||
poetry run ./install/common/download-tessdata.py
|
||||
|
||||
# Copy the container image to the assets folder
|
||||
cp share/container.tar.gz ~dz/release-assets/$VERSION/dangerzone-$VERSION-arm64.tar.gz
|
||||
cp share/image-id.txt ~dz/release-assets/$VERSION/.
|
||||
```
|
||||
Then copy the `share/container.tar.gz` to the assets folder on `dangerzone-$VERSION-arm64.tar.gz`, along with the `share/image-id.txt` file.
|
||||
- [ ] Run `poetry run ./install/macos/build-app.py`; this will make `dist/Dangerzone.app`
|
||||
- [ ] Make sure that the build application works with the containerd graph
|
||||
driver (see [#933](https://github.com/freedomofpress/dangerzone/issues/933))
|
||||
- [ ] Run `poetry run ./install/macos/build-app.py --only-codesign`; this will make `dist/Dangerzone.dmg`
|
||||
* You need to run this command as the account that has access to the code signing certificate
|
||||
* You must run this command from the MacOS UI, from a terminal application.
|
||||
- [ ] Notarize it: `xcrun notarytool submit --wait --apple-id "<email>" --keychain-profile "dz-notarytool-release-key" dist/Dangerzone.dmg`
|
||||
* You need to change the `<email>` in the above command with the email
|
||||
associated with the Apple Developer ID.
|
||||
* This command assumes that you have created, and stored in the Keychain, an
|
||||
application password associated with your Apple Developer ID, which will be
|
||||
used specifically for `notarytool`.
|
||||
- [ ] Wait for it to get approved:
|
||||
* If it gets rejected, you should be able to see why with the same command
|
||||
(or use the `log` option for a more verbose JSON output)
|
||||
* You will also receive an update in your email.
|
||||
- [ ] After it's approved, staple the ticket: `xcrun stapler staple dist/Dangerzone.dmg`
|
||||
|
||||
This process ends up with the final file:
|
||||
- [ ] Build the app bundle
|
||||
|
||||
```
|
||||
dist/Dangerzone.dmg
|
||||
```
|
||||
```bash
|
||||
poetry run ./install/macos/build-app.py
|
||||
```
|
||||
|
||||
Rename `Dangerzone.dmg` to `Dangerzone-$VERSION.dmg`.
|
||||
- [ ] Sign the application bundle, and notarize it
|
||||
|
||||
You need to run this command as the account that has access to the code signing certificate
|
||||
|
||||
This command assumes that you have created, and stored in the Keychain, an
|
||||
application password associated with your Apple Developer ID, which will be
|
||||
used specifically for `notarytool`.
|
||||
|
||||
```bash
|
||||
# Sign the .App and make it a .dmg
|
||||
poetry run ./install/macos/build-app.py --only-codesign
|
||||
|
||||
# Notarize it. You must run this command from the MacOS UI
|
||||
# from a terminal application.
|
||||
xcrun notarytool submit ./dist/Dangerzone.dmg --apple-id $APPLE_ID --keychain-profile "dz-notarytool-release-key" --wait && xcrun stapler staple dist/Dangerzone.dmg
|
||||
|
||||
# Copy the .dmg to the assets folder
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
ARCH="i686"
|
||||
fi
|
||||
cp dist/Dangerzone.dmg ~dz/release-assets/$VERSION/Dangerzone-$VERSION-$ARCH.dmg
|
||||
```
|
||||
|
||||
### Windows Release
|
||||
|
||||
The Windows release is performed in a Windows 11 virtual machine as opposed to a physical one.
|
||||
The Windows release is performed in a Windows 11 virtual machine (as opposed to a physical one).
|
||||
|
||||
#### Initial Setup
|
||||
|
||||
|
@ -321,14 +191,34 @@ The Windows release is performed in a Windows 11 virtual machine as opposed to a
|
|||
|
||||
#### Releasing and Signing
|
||||
|
||||
- [ ] Verify and checkout the git tag for this release
|
||||
- [ ] Run `poetry install --sync`
|
||||
- [ ] Checkout the dependencies, and clean your local copy:
|
||||
```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
|
||||
|
||||
# You can verify the correct Python version is used
|
||||
poetry debug info
|
||||
|
||||
# Replace with the actual version
|
||||
export DZ_VERSION=$(cat share/version.txt)
|
||||
|
||||
# Verify and checkout the git tag for this release:
|
||||
git checkout -f v$VERSION
|
||||
|
||||
# Clean the git repository
|
||||
git clean -df
|
||||
|
||||
# Clean up the environment
|
||||
poetry env remove --all
|
||||
|
||||
# Install the dependencies
|
||||
poetry install --sync
|
||||
```
|
||||
|
||||
- [ ] Copy the container image into the VM
|
||||
> [!IMPORTANT]
|
||||
> Instead of running `python .\install\windows\build-image.py` in the VM, run the build image script on the host (making sure to build for `linux/amd64`). Copy `share/container.tar.gz` and `share/image-id.txt` from the host into the `share` folder in the VM.
|
||||
> Also, don't forget to add the supplementary image ID (see
|
||||
> [#933](https://github.com/freedomofpress/dangerzone/issues/933)) in
|
||||
> `share/image-id.txt`)
|
||||
- [ ] Run `poetry run .\install\windows\build-app.bat`
|
||||
- [ ] When you're done you will have `dist\Dangerzone.msi`
|
||||
|
||||
|
@ -336,12 +226,16 @@ Rename `Dangerzone.msi` to `Dangerzone-$VERSION.msi`.
|
|||
|
||||
### Linux release
|
||||
|
||||
> [!INFO]
|
||||
> Below we explain how we build packages for each Linux distribution we support.
|
||||
> [!TIP]
|
||||
> You can automate these steps from any Linux distribution with:
|
||||
>
|
||||
> There is also a `release.sh` script available which creates all
|
||||
> the `.rpm` and `.deb` files with a single command.
|
||||
> ```
|
||||
> make build-linux
|
||||
> ```
|
||||
>
|
||||
> You can then add the created artifacts to the appropriate APT/YUM repo.
|
||||
|
||||
Below we explain how we build packages for each Linux distribution we support.
|
||||
|
||||
#### Debian/Ubuntu
|
||||
|
||||
|
@ -354,21 +248,15 @@ instructions in our build section](https://github.com/freedomofpress/dangerzone/
|
|||
or create your own locally with:
|
||||
|
||||
```sh
|
||||
# Create and run debian bookworm development environment
|
||||
./dev_scripts/env.py --distro debian --version bookworm build-dev
|
||||
./dev_scripts/env.py --distro debian --version bookworm run --dev bash
|
||||
cd dangerzone
|
||||
```
|
||||
|
||||
Build the latest container:
|
||||
# Build the latest container
|
||||
./dev_scripts/env.py --distro debian --version bookworm run --dev bash -c "cd dangerzone && poetry run ./install/common/build-image.py"
|
||||
|
||||
```sh
|
||||
python3 ./install/common/build-image.py
|
||||
```
|
||||
|
||||
Create a .deb:
|
||||
|
||||
```sh
|
||||
./install/linux/build-deb.py
|
||||
# Create a .deb
|
||||
./dev_scripts/env.py --distro debian --version bookworm run --dev bash -c "cd dangerzone && ./install/linux/build-deb.py"
|
||||
```
|
||||
|
||||
Publish the .deb under `./deb_dist` to the
|
||||
|
@ -387,22 +275,12 @@ or create your own locally with:
|
|||
|
||||
```sh
|
||||
./dev_scripts/env.py --distro fedora --version 41 build-dev
|
||||
./dev_scripts/env.py --distro fedora --version 41 run --dev bash
|
||||
cd dangerzone
|
||||
```
|
||||
|
||||
Build the latest container:
|
||||
# Build the latest container (skip if already built):
|
||||
./dev_scripts/env.py --distro fedora --version 41 run --dev bash -c "cd dangerzone && poetry run ./install/common/build-image.py"
|
||||
|
||||
```sh
|
||||
python3 ./install/common/build-image.py
|
||||
```
|
||||
|
||||
Copy the container image to the assets folder on `dangerzone-$VERSION-i686.tar.gz`.
|
||||
|
||||
Create a .rpm:
|
||||
|
||||
```sh
|
||||
./install/linux/build-rpm.py
|
||||
# Create a .rpm:
|
||||
./dev_scripts/env.py --distro fedora --version 41 run --dev bash -c "cd dangerzone && ./install/linux/build-rpm.py"
|
||||
```
|
||||
|
||||
Publish the .rpm under `./dist` to the
|
||||
|
@ -413,7 +291,7 @@ Publish the .rpm under `./dist` to the
|
|||
Create a .rpm for Qubes:
|
||||
|
||||
```sh
|
||||
./install/linux/build-rpm.py --qubes
|
||||
./dev_scripts/env.py --distro fedora --version 41 run --dev bash -c "cd dangerzone && ./install/linux/build-rpm.py --qubes"
|
||||
```
|
||||
|
||||
and similarly publish it to the [`freedomofpress/yum-tools-prod`](https://github.com/freedomofpress/yum-tools-prod)
|
||||
|
@ -421,36 +299,37 @@ repo.
|
|||
|
||||
## Publishing the Release
|
||||
|
||||
To publish the release:
|
||||
To publish the release, you can follow these steps:
|
||||
|
||||
- [ ] Create an archive of the Dangerzone source in `tar.gz` format:
|
||||
* You can use the following command:
|
||||
|
||||
```
|
||||
export DZ_VERSION=$(cat share/version.txt)
|
||||
git archive --format=tar.gz -o dangerzone-${DZ_VERSION:?}.tar.gz --prefix=dangerzone/ v${DZ_VERSION:?}
|
||||
```
|
||||
```bash
|
||||
export VERSION=$(cat share/version.txt)
|
||||
git archive --format=tar.gz -o dangerzone-${VERSION:?}.tar.gz --prefix=dangerzone/ v${VERSION:?}
|
||||
```
|
||||
|
||||
- [ ] Run container scan on the produced container images (some time may have passed since the artifacts were built)
|
||||
```
|
||||
```bash
|
||||
gunzip --keep -c ./share/container.tar.gz > /tmp/container.tar
|
||||
docker pull anchore/grype:latest
|
||||
docker run --rm -v /tmp/container.tar:/container.tar anchore/grype:latest /container.tar
|
||||
```
|
||||
|
||||
- [ ] Collect the assets in a single directory, calculate their SHA-256 hashes, and sign them.
|
||||
* You can use `./dev_scripts/sign-assets.py`, if you want to automate this
|
||||
task.
|
||||
- [ ] Create a new **draft** release on GitHub and upload the macOS and Windows installers.
|
||||
* Copy the release notes text from the template at [`docs/templates/release-notes`](https://github.com/freedomofpress/dangerzone/tree/main/docs/templates/)
|
||||
* You can use `./dev_scripts/upload-asset.py`, if you want to upload an asset
|
||||
using an access token.
|
||||
- [ ] Upload the `container-$VERSION-i686.tar.gz` and `container-$VERSION-arm64.tar.gz` images that were created in the previous step
|
||||
There is an `./dev_scripts/sign-assets.py` script to automate this task.
|
||||
|
||||
**Important:** Make sure that it's the same container images as the ones that
|
||||
are shipped in other platforms (see our [Pre-release](#Pre-release) section)
|
||||
**Important:** Before running the script, make sure that it's the same container images as
|
||||
the ones that are shipped in other platforms (see our [Pre-release](#Pre-release) section)
|
||||
|
||||
```bash
|
||||
# Sign all the assets
|
||||
./dev_scripts/sign-assets.py ~/release-assets/$VERSION/github --version $VERSION
|
||||
```
|
||||
|
||||
- [ ] Upload all the assets to the draft release on GitHub.
|
||||
```bash
|
||||
find ~/release-assets/$VERSION/github | xargs -n1 ./dev_scripts/upload-asset.py --token ~/token --draft
|
||||
```
|
||||
|
||||
- [ ] Upload the detached signatures (.asc) and checksum file.
|
||||
- [ ] Update the [Dangerzone website](https://github.com/freedomofpress/dangerzone.rocks) to link to the new installers.
|
||||
- [ ] Update the brew cask release of Dangerzone with a [PR like this one](https://github.com/Homebrew/homebrew-cask/pull/116319)
|
||||
- [ ] Update version and download links in `README.md`
|
||||
|
|
|
@ -42,6 +42,11 @@ def print_header(s: str) -> None:
|
|||
type=click.UNPROCESSED,
|
||||
callback=args.validate_input_filenames,
|
||||
)
|
||||
@click.option(
|
||||
"--debug",
|
||||
"debug",
|
||||
flag_value=True,
|
||||
help="Run Dangerzone in debug mode, to get logs from gVisor.")
|
||||
@click.version_option(version=get_version(), message="%(version)s")
|
||||
@errors.handle_document_errors
|
||||
def cli_main(
|
||||
|
@ -50,6 +55,7 @@ def cli_main(
|
|||
filenames: List[str],
|
||||
archive: bool,
|
||||
dummy_conversion: bool,
|
||||
debug: bool,
|
||||
) -> None:
|
||||
setup_logging()
|
||||
|
||||
|
@ -58,7 +64,7 @@ def cli_main(
|
|||
elif is_qubes_native_conversion():
|
||||
dangerzone = DangerzoneCore(Qubes())
|
||||
else:
|
||||
dangerzone = DangerzoneCore(Container())
|
||||
dangerzone = DangerzoneCore(Container(debug=debug))
|
||||
|
||||
display_banner()
|
||||
if len(filenames) == 1 and output_filename:
|
||||
|
|
149
dangerzone/container_utils.py
Normal file
149
dangerzone/container_utils.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
import gzip
|
||||
import logging
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import List, Tuple
|
||||
|
||||
from . import errors
|
||||
from .util import get_resource_path, get_subprocess_startupinfo
|
||||
|
||||
CONTAINER_NAME = "dangerzone.rocks/dangerzone"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_runtime_name() -> str:
|
||||
if platform.system() == "Linux":
|
||||
runtime_name = "podman"
|
||||
else:
|
||||
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually
|
||||
runtime_name = "docker"
|
||||
return runtime_name
|
||||
|
||||
|
||||
def get_runtime_version() -> Tuple[int, int]:
|
||||
"""Get the major/minor parts of the Docker/Podman version.
|
||||
|
||||
Some of the operations we perform in this module rely on some Podman features
|
||||
that are not available across all of our platforms. In order to have a proper
|
||||
fallback, we need to know the Podman version. More specifically, we're fine with
|
||||
just knowing the major and minor version, since writing/installing a full-blown
|
||||
semver parser is an overkill.
|
||||
"""
|
||||
# Get the Docker/Podman version, using a Go template.
|
||||
runtime = get_runtime_name()
|
||||
if runtime == "podman":
|
||||
query = "{{.Client.Version}}"
|
||||
else:
|
||||
query = "{{.Server.Version}}"
|
||||
|
||||
cmd = [runtime, "version", "-f", query]
|
||||
try:
|
||||
version = subprocess.run(
|
||||
cmd,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
capture_output=True,
|
||||
check=True,
|
||||
).stdout.decode()
|
||||
except Exception as e:
|
||||
msg = f"Could not get the version of the {runtime.capitalize()} tool: {e}"
|
||||
raise RuntimeError(msg) from e
|
||||
|
||||
# Parse this version and return the major/minor parts, since we don't need the
|
||||
# rest.
|
||||
try:
|
||||
major, minor, _ = version.split(".", 3)
|
||||
return (int(major), int(minor))
|
||||
except Exception as e:
|
||||
msg = (
|
||||
f"Could not parse the version of the {runtime.capitalize()} tool"
|
||||
f" (found: '{version}') due to the following error: {e}"
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def get_runtime() -> str:
|
||||
container_tech = get_runtime_name()
|
||||
runtime = shutil.which(container_tech)
|
||||
if runtime is None:
|
||||
raise errors.NoContainerTechException(container_tech)
|
||||
return runtime
|
||||
|
||||
|
||||
def list_image_tags() -> List[str]:
|
||||
"""Get the tags of all loaded Dangerzone images.
|
||||
|
||||
This method returns a mapping of image tags to image IDs, for all Dangerzone
|
||||
images. This can be useful when we want to find which are the local image tags,
|
||||
and which image ID does the "latest" tag point to.
|
||||
"""
|
||||
return (
|
||||
subprocess.check_output(
|
||||
[
|
||||
get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{ .Tag }}",
|
||||
CONTAINER_NAME,
|
||||
],
|
||||
text=True,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
.strip()
|
||||
.split()
|
||||
)
|
||||
|
||||
|
||||
def delete_image_tag(tag: str) -> None:
|
||||
"""Delete a Dangerzone image tag."""
|
||||
name = CONTAINER_NAME + ":" + tag
|
||||
log.warning(f"Deleting old container image: {name}")
|
||||
try:
|
||||
subprocess.check_output(
|
||||
[get_runtime(), "rmi", "--force", name],
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
f"Couldn't delete old container image '{name}', so leaving it there."
|
||||
f" Original error: {e}"
|
||||
)
|
||||
|
||||
|
||||
def get_expected_tag() -> str:
|
||||
"""Get the tag of the Dangerzone image tarball from the image-id.txt file."""
|
||||
with open(get_resource_path("image-id.txt")) as f:
|
||||
return f.read().strip()
|
||||
|
||||
|
||||
def load_image_tarball() -> None:
|
||||
log.info("Installing Dangerzone container image...")
|
||||
p = subprocess.Popen(
|
||||
[get_runtime(), "load"],
|
||||
stdin=subprocess.PIPE,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
|
||||
chunk_size = 4 << 20
|
||||
compressed_container_path = get_resource_path("container.tar.gz")
|
||||
with gzip.open(compressed_container_path) as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if len(chunk) > 0:
|
||||
if p.stdin:
|
||||
p.stdin.write(chunk)
|
||||
else:
|
||||
break
|
||||
_, err = p.communicate()
|
||||
if p.returncode < 0:
|
||||
if err:
|
||||
error = err.decode()
|
||||
else:
|
||||
error = "No output"
|
||||
raise errors.ImageInstallationException(
|
||||
f"Could not install container image: {error}"
|
||||
)
|
||||
|
||||
log.info("Successfully installed container image from")
|
|
@ -117,3 +117,26 @@ def handle_document_errors(func: F) -> F:
|
|||
sys.exit(1)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
|
||||
#### Container-related errors
|
||||
|
||||
|
||||
class ImageNotPresentException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImageInstallationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoContainerTechException(Exception):
|
||||
def __init__(self, container_tech: str) -> None:
|
||||
super().__init__(f"{container_tech} is not installed")
|
||||
|
||||
|
||||
class NotAvailableContainerTechException(Exception):
|
||||
def __init__(self, container_tech: str, error: str) -> None:
|
||||
self.error = error
|
||||
self.container_tech = container_tech
|
||||
super().__init__(f"{container_tech} is not available")
|
||||
|
|
|
@ -25,13 +25,7 @@ else:
|
|||
|
||||
from .. import errors
|
||||
from ..document import SAFE_EXTENSION, Document
|
||||
from ..isolation_provider.container import (
|
||||
Container,
|
||||
NoContainerTechException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
from ..isolation_provider.dummy import Dummy
|
||||
from ..isolation_provider.qubes import Qubes, is_qubes_native_conversion
|
||||
from ..isolation_provider.qubes import is_qubes_native_conversion
|
||||
from ..util import format_exception, get_resource_path, get_version
|
||||
from .logic import Alert, CollapsibleBox, DangerzoneGui, UpdateDialog
|
||||
from .updater import UpdateReport
|
||||
|
@ -197,14 +191,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
header_layout.addWidget(self.hamburger_button)
|
||||
header_layout.addSpacing(15)
|
||||
|
||||
if isinstance(self.dangerzone.isolation_provider, Container):
|
||||
if self.dangerzone.isolation_provider.should_wait_install():
|
||||
# Waiting widget replaces content widget while container runtime isn't available
|
||||
self.waiting_widget: WaitingWidget = WaitingWidgetContainer(self.dangerzone)
|
||||
self.waiting_widget.finished.connect(self.waiting_finished)
|
||||
|
||||
elif isinstance(self.dangerzone.isolation_provider, Dummy) or isinstance(
|
||||
self.dangerzone.isolation_provider, Qubes
|
||||
):
|
||||
else:
|
||||
# Don't wait with dummy converter and on Qubes.
|
||||
self.waiting_widget = WaitingWidget()
|
||||
self.dangerzone.is_waiting_finished = True
|
||||
|
@ -500,11 +491,11 @@ class WaitingWidgetContainer(WaitingWidget):
|
|||
error: Optional[str] = None
|
||||
|
||||
try:
|
||||
self.dangerzone.isolation_provider.is_runtime_available()
|
||||
except NoContainerTechException as e:
|
||||
self.dangerzone.isolation_provider.is_available()
|
||||
except errors.NoContainerTechException as e:
|
||||
log.error(str(e))
|
||||
state = "not_installed"
|
||||
except NotAvailableContainerTechException as e:
|
||||
except errors.NotAvailableContainerTechException as e:
|
||||
log.error(str(e))
|
||||
state = "not_running"
|
||||
error = e.error
|
||||
|
|
|
@ -6,7 +6,6 @@ import signal
|
|||
import subprocess
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import IO, Callable, Iterator, Optional
|
||||
|
||||
import fitz
|
||||
|
@ -87,12 +86,16 @@ class IsolationProvider(ABC):
|
|||
Abstracts an isolation provider
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
if getattr(sys, "dangerzone_dev", False) is True:
|
||||
def __init__(self, debug: bool = False) -> None:
|
||||
self.debug = debug
|
||||
if self.should_capture_stderr():
|
||||
self.proc_stderr = subprocess.PIPE
|
||||
else:
|
||||
self.proc_stderr = subprocess.DEVNULL
|
||||
|
||||
def should_capture_stderr(self) -> bool:
|
||||
return self.debug or getattr(sys, "dangerzone_dev", False)
|
||||
|
||||
@staticmethod
|
||||
def is_runtime_available() -> bool:
|
||||
return True
|
||||
|
@ -258,6 +261,16 @@ class IsolationProvider(ABC):
|
|||
)
|
||||
return errors.exception_from_error_code(error_code)
|
||||
|
||||
@abstractmethod
|
||||
def should_wait_install(self) -> bool:
|
||||
"""Whether this isolation provider takes a lot of time to install."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_available(self) -> bool:
|
||||
"""Whether the backing implementation of the isolation provider is available."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_max_parallel_conversions(self) -> int:
|
||||
pass
|
||||
|
@ -339,9 +352,9 @@ class IsolationProvider(ABC):
|
|||
)
|
||||
|
||||
# Read the stderr of the process only if:
|
||||
# * Dev mode is enabled.
|
||||
# * We're in debug mode
|
||||
# * The process has exited (else we risk hanging).
|
||||
if getattr(sys, "dangerzone_dev", False) and p.poll() is not None:
|
||||
if self.should_capture_stderr() and p.poll() is not None:
|
||||
assert p.stderr
|
||||
debug_log = read_debug_text(p.stderr, MAX_CONVERSION_LOG_CHARS)
|
||||
log.info(
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import gzip
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import List, Tuple
|
||||
from typing import List
|
||||
|
||||
from .. import container_utils, errors
|
||||
from ..document import Document
|
||||
from ..util import get_resource_path, get_subprocess_startupinfo
|
||||
from .base import IsolationProvider, terminate_process_group
|
||||
|
@ -25,88 +24,8 @@ else:
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NoContainerTechException(Exception):
|
||||
def __init__(self, container_tech: str) -> None:
|
||||
super().__init__(f"{container_tech} is not installed")
|
||||
|
||||
|
||||
class NotAvailableContainerTechException(Exception):
|
||||
def __init__(self, container_tech: str, error: str) -> None:
|
||||
self.error = error
|
||||
self.container_tech = container_tech
|
||||
super().__init__(f"{container_tech} is not available")
|
||||
|
||||
|
||||
class ImageNotPresentException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImageInstallationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Container(IsolationProvider):
|
||||
# Name of the dangerzone container
|
||||
CONTAINER_NAME = "dangerzone.rocks/dangerzone"
|
||||
|
||||
@staticmethod
|
||||
def get_runtime_name() -> str:
|
||||
if platform.system() == "Linux":
|
||||
runtime_name = "podman"
|
||||
else:
|
||||
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually
|
||||
runtime_name = "docker"
|
||||
return runtime_name
|
||||
|
||||
@staticmethod
|
||||
def get_runtime_version() -> Tuple[int, int]:
|
||||
"""Get the major/minor parts of the Docker/Podman version.
|
||||
|
||||
Some of the operations we perform in this module rely on some Podman features
|
||||
that are not available across all of our platforms. In order to have a proper
|
||||
fallback, we need to know the Podman version. More specifically, we're fine with
|
||||
just knowing the major and minor version, since writing/installing a full-blown
|
||||
semver parser is an overkill.
|
||||
"""
|
||||
# Get the Docker/Podman version, using a Go template.
|
||||
runtime = Container.get_runtime_name()
|
||||
if runtime == "podman":
|
||||
query = "{{.Client.Version}}"
|
||||
else:
|
||||
query = "{{.Server.Version}}"
|
||||
|
||||
cmd = [runtime, "version", "-f", query]
|
||||
try:
|
||||
version = subprocess.run(
|
||||
cmd,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
capture_output=True,
|
||||
check=True,
|
||||
).stdout.decode()
|
||||
except Exception as e:
|
||||
msg = f"Could not get the version of the {runtime.capitalize()} tool: {e}"
|
||||
raise RuntimeError(msg) from e
|
||||
|
||||
# Parse this version and return the major/minor parts, since we don't need the
|
||||
# rest.
|
||||
try:
|
||||
major, minor, _ = version.split(".", 3)
|
||||
return (int(major), int(minor))
|
||||
except Exception as e:
|
||||
msg = (
|
||||
f"Could not parse the version of the {runtime.capitalize()} tool"
|
||||
f" (found: '{version}') due to the following error: {e}"
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
@staticmethod
|
||||
def get_runtime() -> str:
|
||||
container_tech = Container.get_runtime_name()
|
||||
runtime = shutil.which(container_tech)
|
||||
if runtime is None:
|
||||
raise NoContainerTechException(container_tech)
|
||||
return runtime
|
||||
|
||||
@staticmethod
|
||||
def get_runtime_security_args() -> List[str]:
|
||||
"""Security options applicable to the outer Dangerzone container.
|
||||
|
@ -127,12 +46,12 @@ class Container(IsolationProvider):
|
|||
* Do not log the container's output.
|
||||
* Do not map the host user to the container, with `--userns nomap` (available
|
||||
from Podman 4.1 onwards)
|
||||
- This particular argument is specified in `start_doc_to_pixels_proc()`, but
|
||||
should move here once #748 is merged.
|
||||
"""
|
||||
if Container.get_runtime_name() == "podman":
|
||||
if container_utils.get_runtime_name() == "podman":
|
||||
security_args = ["--log-driver", "none"]
|
||||
security_args += ["--security-opt", "no-new-privileges"]
|
||||
if container_utils.get_runtime_version() >= (4, 1):
|
||||
security_args += ["--userns", "nomap"]
|
||||
else:
|
||||
security_args = ["--security-opt=no-new-privileges:true"]
|
||||
|
||||
|
@ -156,51 +75,52 @@ class Container(IsolationProvider):
|
|||
|
||||
@staticmethod
|
||||
def install() -> bool:
|
||||
"""Install the container image tarball, or verify that it's already installed.
|
||||
|
||||
Perform the following actions:
|
||||
1. Get the tags of any locally available images that match Dangerzone's image
|
||||
name.
|
||||
2. Get the expected image tag from the image-id.txt file.
|
||||
- If this tag is present in the local images, then we can return.
|
||||
- Else, prune the older container images and continue.
|
||||
3. Load the image tarball and make sure it matches the expected tag.
|
||||
"""
|
||||
Make sure the podman container is installed. Linux only.
|
||||
"""
|
||||
if Container.is_container_installed():
|
||||
old_tags = container_utils.list_image_tags()
|
||||
expected_tag = container_utils.get_expected_tag()
|
||||
|
||||
if expected_tag not in old_tags:
|
||||
# Prune older container images.
|
||||
log.info(
|
||||
f"Could not find a Dangerzone container image with tag '{expected_tag}'"
|
||||
)
|
||||
for tag in old_tags:
|
||||
container_utils.delete_image_tag(tag)
|
||||
else:
|
||||
return True
|
||||
|
||||
# Load the container into podman
|
||||
log.info("Installing Dangerzone container image...")
|
||||
# Load the image tarball into the container runtime.
|
||||
container_utils.load_image_tarball()
|
||||
|
||||
p = subprocess.Popen(
|
||||
[Container.get_runtime(), "load"],
|
||||
stdin=subprocess.PIPE,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
|
||||
chunk_size = 4 << 20
|
||||
compressed_container_path = get_resource_path("container.tar.gz")
|
||||
with gzip.open(compressed_container_path) as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if len(chunk) > 0:
|
||||
if p.stdin:
|
||||
p.stdin.write(chunk)
|
||||
else:
|
||||
break
|
||||
_, err = p.communicate()
|
||||
if p.returncode < 0:
|
||||
if err:
|
||||
error = err.decode()
|
||||
else:
|
||||
error = "No output"
|
||||
raise ImageInstallationException(
|
||||
f"Could not install container image: {error}"
|
||||
# Check that the container image has the expected image tag.
|
||||
# See https://github.com/freedomofpress/dangerzone/issues/988 for an example
|
||||
# where this was not the case.
|
||||
new_tags = container_utils.list_image_tags()
|
||||
if expected_tag not in new_tags:
|
||||
raise errors.ImageNotPresentException(
|
||||
f"Could not find expected tag '{expected_tag}' after loading the"
|
||||
" container image tarball"
|
||||
)
|
||||
|
||||
if not Container.is_container_installed(raise_on_error=True):
|
||||
return False
|
||||
|
||||
log.info("Container image installed")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_runtime_available() -> bool:
|
||||
container_runtime = Container.get_runtime()
|
||||
runtime_name = Container.get_runtime_name()
|
||||
def should_wait_install() -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_available() -> bool:
|
||||
container_runtime = container_utils.get_runtime()
|
||||
runtime_name = container_utils.get_runtime_name()
|
||||
# Can we run `docker/podman image ls` without an error
|
||||
with subprocess.Popen(
|
||||
[container_runtime, "image", "ls"],
|
||||
|
@ -210,61 +130,11 @@ class Container(IsolationProvider):
|
|||
) as p:
|
||||
_, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise NotAvailableContainerTechException(runtime_name, stderr.decode())
|
||||
raise errors.NotAvailableContainerTechException(
|
||||
runtime_name, stderr.decode()
|
||||
)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_container_installed(raise_on_error: bool = False) -> bool:
|
||||
"""
|
||||
See if the container is installed.
|
||||
"""
|
||||
# Get the image id
|
||||
with open(get_resource_path("image-id.txt")) as f:
|
||||
expected_image_ids = f.read().strip().split()
|
||||
|
||||
# See if this image is already installed
|
||||
installed = False
|
||||
found_image_id = subprocess.check_output(
|
||||
[
|
||||
Container.get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
Container.CONTAINER_NAME,
|
||||
],
|
||||
text=True,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
found_image_id = found_image_id.strip()
|
||||
|
||||
if found_image_id in expected_image_ids:
|
||||
installed = True
|
||||
elif found_image_id == "":
|
||||
if raise_on_error:
|
||||
raise ImageNotPresentException(
|
||||
"Image is not listed after installation. Bailing out."
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
f"{Container.CONTAINER_NAME} images found, but IDs do not match."
|
||||
f" Found: {found_image_id}, Expected: {','.join(expected_image_ids)}"
|
||||
)
|
||||
if raise_on_error:
|
||||
raise ImageNotPresentException(msg)
|
||||
log.info(msg)
|
||||
log.info("Deleting old dangerzone container image")
|
||||
|
||||
try:
|
||||
subprocess.check_output(
|
||||
[Container.get_runtime(), "rmi", "--force", found_image_id],
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
except Exception:
|
||||
log.warning("Couldn't delete old container image, so leaving it there")
|
||||
|
||||
return installed
|
||||
|
||||
def doc_to_pixels_container_name(self, document: Document) -> str:
|
||||
"""Unique container name for the doc-to-pixels phase."""
|
||||
return f"dangerzone-doc-to-pixels-{document.id}"
|
||||
|
@ -295,21 +165,27 @@ class Container(IsolationProvider):
|
|||
self,
|
||||
command: List[str],
|
||||
name: str,
|
||||
extra_args: List[str] = [],
|
||||
) -> subprocess.Popen:
|
||||
container_runtime = self.get_runtime()
|
||||
container_runtime = container_utils.get_runtime()
|
||||
security_args = self.get_runtime_security_args()
|
||||
debug_args = []
|
||||
if self.debug:
|
||||
debug_args += ["-e", "RUNSC_DEBUG=1"]
|
||||
|
||||
enable_stdin = ["-i"]
|
||||
set_name = ["--name", name]
|
||||
prevent_leakage_args = ["--rm"]
|
||||
image_name = [
|
||||
container_utils.CONTAINER_NAME + ":" + container_utils.get_expected_tag()
|
||||
]
|
||||
args = (
|
||||
["run"]
|
||||
+ security_args
|
||||
+ debug_args
|
||||
+ prevent_leakage_args
|
||||
+ enable_stdin
|
||||
+ set_name
|
||||
+ extra_args
|
||||
+ [self.CONTAINER_NAME]
|
||||
+ image_name
|
||||
+ command
|
||||
)
|
||||
args = [container_runtime] + args
|
||||
|
@ -325,7 +201,7 @@ class Container(IsolationProvider):
|
|||
connected to the Docker daemon, and killing it will just close the associated
|
||||
standard streams.
|
||||
"""
|
||||
container_runtime = self.get_runtime()
|
||||
container_runtime = container_utils.get_runtime()
|
||||
cmd = [container_runtime, "kill", name]
|
||||
try:
|
||||
# We do not check the exit code of the process here, since the container may
|
||||
|
@ -358,15 +234,8 @@ class Container(IsolationProvider):
|
|||
"-m",
|
||||
"dangerzone.conversion.doc_to_pixels",
|
||||
]
|
||||
# NOTE: Using `--userns nomap` is available only on Podman >= 4.1.0.
|
||||
# XXX: Move this under `get_runtime_security_args()` once #748 is merged.
|
||||
extra_args = []
|
||||
if Container.get_runtime_name() == "podman":
|
||||
if Container.get_runtime_version() >= (4, 1):
|
||||
extra_args += ["--userns", "nomap"]
|
||||
|
||||
name = self.doc_to_pixels_container_name(document)
|
||||
return self.exec_container(command, name=name, extra_args=extra_args)
|
||||
return self.exec_container(command, name=name)
|
||||
|
||||
def terminate_doc_to_pixels_proc(
|
||||
self, document: Document, p: subprocess.Popen
|
||||
|
@ -389,7 +258,7 @@ class Container(IsolationProvider):
|
|||
# after a podman kill / docker kill invocation, this will likely be the case,
|
||||
# else the container runtime (Docker/Podman) has experienced a problem, and we
|
||||
# should report it.
|
||||
container_runtime = self.get_runtime()
|
||||
container_runtime = container_utils.get_runtime()
|
||||
name = self.doc_to_pixels_container_name(document)
|
||||
all_containers = subprocess.run(
|
||||
[container_runtime, "ps", "-a"],
|
||||
|
@ -411,11 +280,11 @@ class Container(IsolationProvider):
|
|||
if cpu_count is not None:
|
||||
n_cpu = cpu_count
|
||||
|
||||
elif self.get_runtime_name() == "docker":
|
||||
elif container_utils.get_runtime_name() == "docker":
|
||||
# For Windows and MacOS containers run in VM
|
||||
# So we obtain the CPU count for the VM
|
||||
n_cpu_str = subprocess.check_output(
|
||||
[self.get_runtime(), "info", "--format", "{{.NCPU}}"],
|
||||
[container_utils.get_runtime(), "info", "--format", "{{.NCPU}}"],
|
||||
text=True,
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
|
|
|
@ -39,6 +39,14 @@ class Dummy(IsolationProvider):
|
|||
def install(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_available() -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def should_wait_install() -> bool:
|
||||
return False
|
||||
|
||||
def start_doc_to_pixels_proc(self, document: Document) -> subprocess.Popen:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
|
|
|
@ -21,6 +21,14 @@ class Qubes(IsolationProvider):
|
|||
def install(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_available() -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def should_wait_install() -> bool:
|
||||
return False
|
||||
|
||||
def get_max_parallel_conversions(self) -> int:
|
||||
return 1
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ class DangerzoneCore(object):
|
|||
ocr_lang,
|
||||
stdout_callback,
|
||||
)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
log.exception(
|
||||
f"Unexpected error occurred while converting '{document}'"
|
||||
)
|
||||
|
|
|
@ -8,7 +8,6 @@ import platform
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
from datetime import date
|
||||
|
||||
DEFAULT_GUI = True
|
||||
|
|
67
dev_scripts/generate-release-tasks.py
Executable file
67
dev_scripts/generate-release-tasks.py
Executable file
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
RELEASE_FILE = "RELEASE.md"
|
||||
QA_FILE = "QA.md"
|
||||
|
||||
|
||||
def git_root():
|
||||
"""Get the root directory of the Git repo."""
|
||||
# FIXME: Use a Git Python binding for this.
|
||||
# FIXME: Make this work if called outside the repo.
|
||||
path = (
|
||||
subprocess.run(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
.stdout.decode()
|
||||
.strip("\n")
|
||||
)
|
||||
return pathlib.Path(path)
|
||||
|
||||
|
||||
def extract_checkboxes(filename):
|
||||
headers = []
|
||||
result = []
|
||||
|
||||
with open(filename, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
current_level = 0
|
||||
for line in lines:
|
||||
line = line.rstrip()
|
||||
|
||||
# If it's a header, store it
|
||||
if line.startswith("#"):
|
||||
# Count number of # to determine header level
|
||||
level = len(line) - len(line.lstrip("#"))
|
||||
if level < current_level or not current_level:
|
||||
headers.extend(["", line, ""])
|
||||
current_level = level
|
||||
elif level > current_level:
|
||||
continue
|
||||
else:
|
||||
headers = ["", line, ""]
|
||||
|
||||
# If it's a checkbox
|
||||
elif "- [ ]" in line or "- [x]" in line or "- [X]" in line:
|
||||
# Print the last header if we haven't already
|
||||
if headers:
|
||||
result.extend(headers)
|
||||
headers = []
|
||||
current_level = 0
|
||||
|
||||
# If this is the "Do the QA tasks" line, recursively get QA tasks
|
||||
if "Do the QA tasks" in line:
|
||||
result.append(line)
|
||||
qa_tasks = extract_checkboxes(git_root() / QA_FILE)
|
||||
result.append(qa_tasks)
|
||||
else:
|
||||
result.append(line)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(extract_checkboxes(git_root() / RELEASE_FILE))
|
|
@ -20,17 +20,32 @@ EOL_PYTHON_URL = "https://endoflife.date/api/python.json"
|
|||
CONTENT_QA = r"""## QA
|
||||
|
||||
To ensure that new releases do not introduce regressions, and support existing
|
||||
and newer platforms, we have to do the following:
|
||||
and newer platforms, we have to test that the produced packages work as expected.
|
||||
|
||||
Check the following:
|
||||
|
||||
- [ ] Make sure that the tip of the `main` branch passes the CI tests.
|
||||
- [ ] Make sure that the Apple account has a valid application password and has
|
||||
agreed to the latest Apple terms (see [macOS release](#macos-release)
|
||||
section).
|
||||
|
||||
Because it is repetitive, we wrote a script to help with the QA.
|
||||
It can run the tasks for you, pausing when it needs manual intervention.
|
||||
|
||||
You can run it with a command like:
|
||||
|
||||
```bash
|
||||
poetry run ./dev_scripts/qa.py {distro}-{version}
|
||||
```
|
||||
|
||||
### The checklist
|
||||
|
||||
- [ ] Create a test build in Windows and make sure it works:
|
||||
- [ ] Check if the suggested Python version is still supported.
|
||||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Build and run the Dangerzone .exe
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
|
@ -39,6 +54,7 @@ and newer platforms, we have to do the following:
|
|||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
|
@ -47,6 +63,7 @@ and newer platforms, we have to do the following:
|
|||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create and run an app bundle.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
|
@ -55,6 +72,7 @@ and newer platforms, we have to do the following:
|
|||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create a .deb package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
|
@ -63,6 +81,7 @@ and newer platforms, we have to do the following:
|
|||
- [ ] Create a new development environment with Poetry.
|
||||
- [ ] Build the container image and ensure the development environment uses
|
||||
the new image.
|
||||
- [ ] Download the OCR language data using `./install/common/download-tessdata.py`
|
||||
- [ ] Run the Dangerzone tests.
|
||||
- [ ] Create an .rpm package and install it system-wide.
|
||||
- [ ] Test some QA scenarios (see [Scenarios](#Scenarios) below).
|
||||
|
@ -108,9 +127,9 @@ Close the Dangerzone application and get the container image for that
|
|||
version. For example:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone:latest
|
||||
$ docker images dangerzone.rocks/dangerzone
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone latest <image ID> <date> <size>
|
||||
dangerzone.rocks/dangerzone <tag> <image ID> <date> <size>
|
||||
```
|
||||
|
||||
Then run the version under QA and ensure that the settings remain changed.
|
||||
|
@ -119,9 +138,9 @@ Afterwards check that new docker image was installed by running the same command
|
|||
and seeing the following differences:
|
||||
|
||||
```
|
||||
$ docker images dangerzone.rocks/dangerzone:latest
|
||||
$ docker images dangerzone.rocks/dangerzone
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
dangerzone.rocks/dangerzone latest <different ID> <newer date> <different size>
|
||||
dangerzone.rocks/dangerzone <other tag> <different ID> <newer date> <different size>
|
||||
```
|
||||
|
||||
#### 4. Dangerzone successfully installs the container image
|
||||
|
@ -553,7 +572,7 @@ class Reference:
|
|||
# Convert spaces to dashes
|
||||
anchor = anchor.replace(" ", "-")
|
||||
# Remove non-alphanumeric (except dash and underscore)
|
||||
anchor = re.sub("[^a-zA-Z\-_]", "", anchor)
|
||||
anchor = re.sub("[^a-zA-Z-_]", "", anchor)
|
||||
|
||||
return anchor
|
||||
|
||||
|
@ -572,8 +591,8 @@ class QABase(abc.ABC):
|
|||
|
||||
platforms = {}
|
||||
|
||||
REF_QA = Reference("RELEASE.md", content=CONTENT_QA)
|
||||
REF_QA_SCENARIOS = Reference("RELEASE.md", content=CONTENT_QA_SCENARIOS)
|
||||
REF_QA = Reference("QA.md", content=CONTENT_QA)
|
||||
REF_QA_SCENARIOS = Reference("QA.md", content=CONTENT_QA_SCENARIOS)
|
||||
|
||||
# The following class method is available since Python 3.6. For more details, see:
|
||||
# https://docs.python.org/3.6/whatsnew/3.6.html#pep-487-simpler-customization-of-class-creation
|
||||
|
@ -1051,6 +1070,10 @@ class QAFedora(QALinux):
|
|||
)
|
||||
|
||||
|
||||
class QAFedora41(QAFedora):
|
||||
VERSION = "41"
|
||||
|
||||
|
||||
class QAFedora40(QAFedora):
|
||||
VERSION = "40"
|
||||
|
||||
|
|
|
@ -95,11 +95,11 @@ def main():
|
|||
parser.add_argument(
|
||||
"--version",
|
||||
required=True,
|
||||
help=f"look for assets with this Dangerzone version",
|
||||
help="look for assets with this Dangerzone version",
|
||||
)
|
||||
parser.add_argument(
|
||||
"dir",
|
||||
help=f"look for assets in this directory",
|
||||
help="look for assets in this directory",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
setup_logging()
|
||||
|
|
67
docs/developer/doit.md
Normal file
67
docs/developer/doit.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Using the Doit Automation Tool
|
||||
|
||||
Developers can use the [Doit](https://pydoit.org/) automation tool to create
|
||||
release artifacts. The purpose of the tool is to automate the manual release
|
||||
instructions in `RELEASE.md` file. Not everything is automated yet, since we're
|
||||
still experimenting with this tool. You can find our task definitions in this
|
||||
repo's `dodo.py` file.
|
||||
|
||||
## Why Doit?
|
||||
|
||||
We picked Doit out of the various tools out there for the following reasons:
|
||||
|
||||
* **Pythonic:** The configuration file and tasks can be written in Python. Where
|
||||
applicable, it's easy to issue shell commands as well.
|
||||
* **File targets:** Doit borrows the file target concept from Makefiles. Tasks
|
||||
can have file dependencies, and targets they build. This makes it easy to
|
||||
define a dependency graph for tasks.
|
||||
* **Hash-based caching:** Unlike Makefiles, doit does not look at the
|
||||
modification timestamp of source/target files, to figure out if it needs to
|
||||
run them. Instead, it hashes those files, and will run a task only if the
|
||||
hash of a file dependency has changed.
|
||||
* **Parallelization:** Tasks can be run in parallel with the `-n` argument,
|
||||
which is similar to `make`'s `-j` argument.
|
||||
|
||||
## How to Doit?
|
||||
|
||||
First, enter your Poetry shell. Then, make sure that your environment is clean,
|
||||
and you have ample disk space. You can run:
|
||||
|
||||
```bash
|
||||
doit clean --dry-run # if you want to see what would happen
|
||||
doit clean # you'll be asked to cofirm that you want to clean everything
|
||||
```
|
||||
|
||||
Finally, you can build all the release artifacts with `doit`, or a specific task
|
||||
with:
|
||||
|
||||
```
|
||||
doit <task>
|
||||
```
|
||||
|
||||
## 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.
|
||||
* You can run `doit info <task>` to see which dependencies are missing.
|
||||
* You can change this line in `pyproject.toml` to `true`, to allow using the
|
||||
Docker/Podman build cache:
|
||||
|
||||
```
|
||||
use_cache = true
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Using caching may speed up image builds, but is not suitable for release
|
||||
> artifacts. The ID of our base container image (Alpine Linux) does not change
|
||||
> that often, but its APK package index does. So, if we use caching, we risk
|
||||
> skipping the `apk upgrade` layer and end up with packages that are days
|
||||
> behind.
|
||||
|
||||
* You can pass the following environment variables to the script, in order to
|
||||
affect some global parameters:
|
||||
- `CONTAINER_RUNTIME`: The container runtime to use. Either `podman` (default)
|
||||
or `docker`.
|
||||
- `RELEASE_DIR`: Where to store the release artifacts. Default path is
|
||||
`~/release-assets/<version>`
|
||||
- `APPLE_ID`: The Apple ID to use when signing/notarizing the macOS DMG.
|
393
dodo.py
Normal file
393
dodo.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
import json
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from doit.action import CmdAction
|
||||
|
||||
ARCH = "arm64" if platform.machine() == "arm64" else "i686"
|
||||
VERSION = open("share/version.txt").read().strip()
|
||||
FEDORA_VERSIONS = ["40", "41"]
|
||||
DEBIAN_VERSIONS = ["bullseye", "focal", "jammy", "mantic", "noble", "trixie"]
|
||||
|
||||
### Global parameters
|
||||
|
||||
CONTAINER_RUNTIME = os.environ.get("CONTAINER_RUNTIME", "podman")
|
||||
DEFAULT_RELEASE_DIR = Path.home() / "release-assets" / VERSION
|
||||
RELEASE_DIR = Path(os.environ.get("RELEASE_DIR", DEFAULT_RELEASE_DIR))
|
||||
APPLE_ID = os.environ.get("APPLE_ID", None)
|
||||
|
||||
### Task Parameters
|
||||
|
||||
PARAM_APPLE_ID = {
|
||||
"name": "apple_id",
|
||||
"long": "apple-id",
|
||||
"default": APPLE_ID,
|
||||
"help": "The Apple developer ID that will be used to sign the .dmg",
|
||||
}
|
||||
|
||||
PARAM_USE_CACHE = {
|
||||
"name": "use_cache",
|
||||
"long": "use-cache",
|
||||
"help": (
|
||||
"Whether to use cached results or not. For reproducibility reasons,"
|
||||
" it's best to leave it to false"
|
||||
),
|
||||
"default": False,
|
||||
}
|
||||
|
||||
### File dependencies
|
||||
#
|
||||
# Define all the file dependencies for our tasks in a single place, since some file
|
||||
# dependencies are shared between tasks.
|
||||
|
||||
|
||||
def list_files(path, recursive=False):
|
||||
"""List files in a directory, and optionally traverse into subdirectories."""
|
||||
glob_fn = Path(path).rglob if recursive else Path(path).glob
|
||||
return [f for f in glob_fn("*") if f.is_file() and not f.suffix == ".pyc"]
|
||||
|
||||
|
||||
def list_language_data():
|
||||
"""List the expected language data that Dangerzone downloads and stores locally."""
|
||||
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
|
||||
|
||||
|
||||
TESSDATA_DEPS = ["install/common/download-tessdata.py", "share/ocr-languages.json"]
|
||||
TESSDATA_TARGETS = list_language_data()
|
||||
|
||||
IMAGE_DEPS = [
|
||||
"Dockerfile",
|
||||
"poetry.lock",
|
||||
*list_files("dangerzone/conversion"),
|
||||
"dangerzone/gvisor_wrapper/entrypoint.py",
|
||||
"install/common/build-image.py",
|
||||
]
|
||||
IMAGE_TARGETS = ["share/container.tar.gz", "share/image-id.txt"]
|
||||
|
||||
SOURCE_DEPS = [
|
||||
*list_files("assets"),
|
||||
*list_files("share"),
|
||||
*list_files("dangerzone", recursive=True),
|
||||
]
|
||||
|
||||
PYTHON_DEPS = ["poetry.lock", "pyproject.toml"]
|
||||
|
||||
DMG_DEPS = [
|
||||
*list_files("install/macos"),
|
||||
*TESSDATA_TARGETS,
|
||||
*IMAGE_TARGETS,
|
||||
*PYTHON_DEPS,
|
||||
*SOURCE_DEPS,
|
||||
]
|
||||
|
||||
LINUX_DEPS = [
|
||||
*list_files("install/linux"),
|
||||
*IMAGE_TARGETS,
|
||||
*PYTHON_DEPS,
|
||||
*SOURCE_DEPS,
|
||||
]
|
||||
|
||||
DEB_DEPS = [*LINUX_DEPS, *list_files("debian")]
|
||||
RPM_DEPS = [*LINUX_DEPS, *list_files("qubes")]
|
||||
|
||||
|
||||
def copy_dir(src, dst):
|
||||
"""Copy a directory to a destination dir, and overwrite it if it exists."""
|
||||
shutil.rmtree(dst, ignore_errors=True)
|
||||
shutil.copytree(src, dst)
|
||||
|
||||
|
||||
def create_release_dir():
|
||||
RELEASE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(RELEASE_DIR / "tmp").mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def build_linux_pkg(distro, version, cwd, qubes=False):
|
||||
"""Generic command for building a .deb/.rpm in a Dangerzone dev environment."""
|
||||
pkg = "rpm" if distro == "fedora" else "deb"
|
||||
cmd = [
|
||||
"python3",
|
||||
"./dev_scripts/env.py",
|
||||
"--distro",
|
||||
distro,
|
||||
"--version",
|
||||
version,
|
||||
"run",
|
||||
"--no-gui",
|
||||
"--dev",
|
||||
f"./dangerzone/install/linux/build-{pkg}.py",
|
||||
]
|
||||
if qubes:
|
||||
cmd += ["--qubes"]
|
||||
return CmdAction(" ".join(cmd), cwd=cwd)
|
||||
|
||||
|
||||
def build_deb(cwd):
|
||||
"""Build a .deb package on Debian Bookworm."""
|
||||
return build_linux_pkg(distro="debian", version="bookworm", cwd=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)
|
||||
|
||||
|
||||
### Tasks
|
||||
|
||||
|
||||
def task_clean_container_runtime():
|
||||
"""Clean the storage space of the container runtime."""
|
||||
return {
|
||||
"actions": None,
|
||||
"clean": [
|
||||
[CONTAINER_RUNTIME, "system", "prune", "-a", "-f"],
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def task_check_container_runtime():
|
||||
"""Test that the container runtime is ready."""
|
||||
return {
|
||||
"actions": [
|
||||
["which", CONTAINER_RUNTIME],
|
||||
[CONTAINER_RUNTIME, "ps"],
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def task_macos_check_cert():
|
||||
"""Test that the Apple developer certificate can be used."""
|
||||
return {
|
||||
"actions": [
|
||||
"xcrun notarytool history --apple-id %(apple_id)s --keychain-profile dz-notarytool-release-key"
|
||||
],
|
||||
"params": [PARAM_APPLE_ID],
|
||||
}
|
||||
|
||||
|
||||
def task_macos_check_system():
|
||||
"""Run macOS specific system checks, as well as the generic ones."""
|
||||
return {
|
||||
"actions": None,
|
||||
"task_dep": ["check_container_runtime", "macos_check_cert"],
|
||||
}
|
||||
|
||||
|
||||
def task_init_release_dir():
|
||||
"""Create a directory for release artifacts."""
|
||||
return {
|
||||
"actions": [create_release_dir],
|
||||
"clean": [f"rm -rf {RELEASE_DIR}"],
|
||||
}
|
||||
|
||||
|
||||
def task_download_tessdata():
|
||||
"""Download the Tesseract data using ./install/common/download-tessdata.py"""
|
||||
return {
|
||||
"actions": ["python install/common/download-tessdata.py"],
|
||||
"file_dep": TESSDATA_DEPS,
|
||||
"targets": TESSDATA_TARGETS,
|
||||
"clean": True,
|
||||
}
|
||||
|
||||
|
||||
def task_build_image():
|
||||
"""Build the container image using ./install/common/build-image.py"""
|
||||
img_src = "share/container.tar.gz"
|
||||
img_dst = RELEASE_DIR / f"container-{VERSION}-{ARCH}.tar.gz" # FIXME: Add arch
|
||||
img_id_src = "share/image-id.txt"
|
||||
img_id_dst = RELEASE_DIR / "image-id.txt" # FIXME: Add arch
|
||||
|
||||
return {
|
||||
"actions": [
|
||||
f"python install/common/build-image.py --use-cache=%(use_cache)s --runtime={CONTAINER_RUNTIME}",
|
||||
["cp", img_src, img_dst],
|
||||
["cp", img_id_src, img_id_dst],
|
||||
],
|
||||
"params": [PARAM_USE_CACHE],
|
||||
"file_dep": IMAGE_DEPS,
|
||||
"targets": [img_src, img_dst, img_id_src, img_id_dst],
|
||||
"task_dep": ["init_release_dir", "check_container_runtime"],
|
||||
"clean": True,
|
||||
}
|
||||
|
||||
|
||||
def task_poetry_install():
|
||||
"""Setup the Poetry environment"""
|
||||
return {"actions": ["poetry install --sync"], "clean": ["poetry env remove --all"]}
|
||||
|
||||
|
||||
def task_macos_build_dmg():
|
||||
"""Build the macOS .dmg file for Dangerzone."""
|
||||
dz_dir = RELEASE_DIR / "tmp" / "macos"
|
||||
dmg_src = dz_dir / "dist" / "Dangerzone.dmg"
|
||||
dmg_dst = RELEASE_DIR / f"Dangerzone-{VERSION}-{ARCH}.dmg" # FIXME: Add -arch
|
||||
|
||||
return {
|
||||
"actions": [
|
||||
(copy_dir, [".", dz_dir]),
|
||||
f"cd {dz_dir} && poetry run install/macos/build-app.py --with-codesign",
|
||||
(
|
||||
"xcrun notarytool submit --wait --apple-id %(apple_id)s"
|
||||
f" --keychain-profile dz-notarytool-release-key {dmg_src}"
|
||||
),
|
||||
f"xcrun stapler staple {dmg_src}",
|
||||
["cp", dmg_src, dmg_dst],
|
||||
["rm", "-rf", dz_dir],
|
||||
],
|
||||
"params": [PARAM_APPLE_ID],
|
||||
"file_dep": DMG_DEPS,
|
||||
"task_dep": [
|
||||
"macos_check_system",
|
||||
"init_release_dir",
|
||||
"poetry_install",
|
||||
"download_tessdata",
|
||||
],
|
||||
"targets": [dmg_src, dmg_dst],
|
||||
"clean": True,
|
||||
}
|
||||
|
||||
|
||||
def task_debian_env():
|
||||
"""Build a Debian Bookworm dev environment."""
|
||||
return {
|
||||
"actions": [
|
||||
[
|
||||
"python3",
|
||||
"./dev_scripts/env.py",
|
||||
"--distro",
|
||||
"debian",
|
||||
"--version",
|
||||
"bookworm",
|
||||
"build-dev",
|
||||
]
|
||||
],
|
||||
"task_dep": ["check_container_runtime"],
|
||||
}
|
||||
|
||||
|
||||
def task_debian_deb():
|
||||
"""Build a Debian package for Debian Bookworm."""
|
||||
dz_dir = RELEASE_DIR / "tmp" / "debian"
|
||||
deb_name = f"dangerzone_{VERSION}-1_amd64.deb"
|
||||
deb_src = dz_dir / "deb_dist" / deb_name
|
||||
deb_dst = RELEASE_DIR / deb_name
|
||||
|
||||
return {
|
||||
"actions": [
|
||||
(copy_dir, [".", dz_dir]),
|
||||
build_deb(cwd=dz_dir),
|
||||
["cp", deb_src, deb_dst],
|
||||
["rm", "-rf", dz_dir],
|
||||
],
|
||||
"file_dep": DEB_DEPS,
|
||||
"task_dep": ["init_release_dir", "debian_env"],
|
||||
"targets": [deb_dst],
|
||||
"clean": True,
|
||||
}
|
||||
|
||||
|
||||
def task_fedora_env():
|
||||
"""Build Fedora dev environments."""
|
||||
for version in FEDORA_VERSIONS:
|
||||
yield {
|
||||
"name": version,
|
||||
"doc": f"Build Fedora {version} dev environments",
|
||||
"actions": [
|
||||
[
|
||||
"python3",
|
||||
"./dev_scripts/env.py",
|
||||
"--distro",
|
||||
"fedora",
|
||||
"--version",
|
||||
version,
|
||||
"build-dev",
|
||||
],
|
||||
],
|
||||
"task_dep": ["check_container_runtime"],
|
||||
}
|
||||
|
||||
|
||||
def task_fedora_rpm():
|
||||
"""Build Fedora packages for every supported version."""
|
||||
for version in FEDORA_VERSIONS:
|
||||
for qubes in (True, False):
|
||||
qubes_ident = "-qubes" if qubes else ""
|
||||
qubes_desc = " for Qubes" if qubes else ""
|
||||
dz_dir = RELEASE_DIR / "tmp" / f"f{version}{qubes_ident}"
|
||||
rpm_names = [
|
||||
f"dangerzone{qubes_ident}-{VERSION}-1.fc{version}.x86_64.rpm",
|
||||
f"dangerzone{qubes_ident}-{VERSION}-1.fc{version}.src.rpm",
|
||||
]
|
||||
rpm_src = [dz_dir / "dist" / rpm_name for rpm_name in rpm_names]
|
||||
rpm_dst = [RELEASE_DIR / rpm_name for rpm_name in rpm_names]
|
||||
|
||||
yield {
|
||||
"name": version + qubes_ident,
|
||||
"doc": f"Build a Fedora {version} package{qubes_desc}",
|
||||
"actions": [
|
||||
(copy_dir, [".", dz_dir]),
|
||||
build_rpm(version, cwd=dz_dir, qubes=qubes),
|
||||
["cp", *rpm_src, RELEASE_DIR],
|
||||
["rm", "-rf", dz_dir],
|
||||
],
|
||||
"file_dep": RPM_DEPS,
|
||||
"task_dep": ["init_release_dir", f"fedora_env:{version}"],
|
||||
"targets": rpm_dst,
|
||||
"clean": True,
|
||||
}
|
||||
|
||||
|
||||
def task_git_archive():
|
||||
"""Build a Git archive of the repo."""
|
||||
target = f"{RELEASE_DIR}/dangerzone-{VERSION}.tar.gz"
|
||||
return {
|
||||
"actions": [
|
||||
f"git archive --format=tar.gz -o {target} --prefix=dangerzone/ v{VERSION}"
|
||||
],
|
||||
"targets": [target],
|
||||
"task_dep": ["init_release_dir"],
|
||||
}
|
||||
|
||||
|
||||
#######################################################################################
|
||||
#
|
||||
# END OF TASKS
|
||||
#
|
||||
# The following task should be the LAST one in the dodo file, so that it runs first when
|
||||
# running `do clean`.
|
||||
|
||||
|
||||
def clean_prompt():
|
||||
ans = input(
|
||||
f"""
|
||||
You have not specified a target to clean.
|
||||
This means that doit will clean the following targets:
|
||||
|
||||
* ALL the containers, images, and build cache in {CONTAINER_RUNTIME.capitalize()}
|
||||
* ALL the built targets and directories
|
||||
|
||||
For a full list of the targets that doit will clean, run: doit clean --dry-run
|
||||
|
||||
Are you sure you want to clean everything (y/N): \
|
||||
"""
|
||||
)
|
||||
if ans.lower() in ["yes", "y"]:
|
||||
return
|
||||
else:
|
||||
print("Exiting...")
|
||||
exit(1)
|
||||
|
||||
|
||||
def task_clean_prompt():
|
||||
"""Make sure that the user really wants to run the clean tasks."""
|
||||
return {
|
||||
"actions": None,
|
||||
"clean": [clean_prompt],
|
||||
}
|
|
@ -2,12 +2,13 @@ import argparse
|
|||
import gzip
|
||||
import os
|
||||
import platform
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
BUILD_CONTEXT = "dangerzone/"
|
||||
TAG = "dangerzone.rocks/dangerzone:latest"
|
||||
IMAGE_NAME = "dangerzone.rocks/dangerzone"
|
||||
REQUIREMENTS_TXT = "container-pip-requirements.txt"
|
||||
if platform.system() in ["Darwin", "Windows"]:
|
||||
CONTAINER_RUNTIME = "docker"
|
||||
|
@ -17,6 +18,17 @@ elif platform.system() == "Linux":
|
|||
ARCH = platform.machine()
|
||||
|
||||
|
||||
def str2bool(v):
|
||||
if isinstance(v, bool):
|
||||
return v
|
||||
if v.lower() in ("yes", "true", "t", "y", "1"):
|
||||
return True
|
||||
elif v.lower() in ("no", "false", "f", "n", "0"):
|
||||
return False
|
||||
else:
|
||||
raise argparse.ArgumentTypeError("Boolean value expected.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
|
@ -39,13 +51,39 @@ def main():
|
|||
)
|
||||
parser.add_argument(
|
||||
"--use-cache",
|
||||
action="store_true",
|
||||
type=str2bool,
|
||||
nargs="?",
|
||||
default=False,
|
||||
const=True,
|
||||
help="Use the builder's cache to speed up the builds (not suitable for release builds)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
tarball_path = Path("share") / "container.tar.gz"
|
||||
image_id_path = Path("share") / "image-id.txt"
|
||||
|
||||
print(f"Building for architecture '{ARCH}'")
|
||||
|
||||
# Designate a unique tag for this image, depending on the Git commit it was created
|
||||
# from:
|
||||
# 1. If created from a Git tag (e.g., 0.8.0), the image tag will be `0.8.0`.
|
||||
# 2. If created from a commit, it will be something like `0.8.0-31-g6bdaa7a`.
|
||||
# 3. If the contents of the Git repo are dirty, we will append a unique identifier
|
||||
# for this run, something like `0.8.0-31-g6bdaa7a-fdcb` or `0.8.0-fdcb`.
|
||||
dirty_ident = secrets.token_hex(2)
|
||||
tag = (
|
||||
subprocess.check_output(
|
||||
["git", "describe", "--long", "--first-parent", f"--dirty=-{dirty_ident}"],
|
||||
)
|
||||
.decode()
|
||||
.strip()[1:] # remove the "v" prefix of the tag.
|
||||
)
|
||||
image_name_tagged = IMAGE_NAME + ":" + tag
|
||||
|
||||
print(f"Will tag the container image as '{image_name_tagged}'")
|
||||
with open(image_id_path, "w") as f:
|
||||
f.write(tag)
|
||||
|
||||
print("Exporting container pip dependencies")
|
||||
with ContainerPipDependencies():
|
||||
if not args.use_cache:
|
||||
|
@ -59,6 +97,7 @@ def main():
|
|||
check=True,
|
||||
)
|
||||
|
||||
# Build the container image, and tag it with the calculated tag
|
||||
print("Building container image")
|
||||
cache_args = [] if args.use_cache else ["--no-cache"]
|
||||
subprocess.run(
|
||||
|
@ -74,7 +113,7 @@ def main():
|
|||
"-f",
|
||||
"Dockerfile",
|
||||
"--tag",
|
||||
TAG,
|
||||
image_name_tagged,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
@ -85,7 +124,7 @@ def main():
|
|||
[
|
||||
CONTAINER_RUNTIME,
|
||||
"save",
|
||||
TAG,
|
||||
image_name_tagged,
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
@ -93,7 +132,7 @@ def main():
|
|||
print("Compressing container image")
|
||||
chunk_size = 4 << 20
|
||||
with gzip.open(
|
||||
"share/container.tar.gz",
|
||||
tarball_path,
|
||||
"wb",
|
||||
compresslevel=args.compress_level,
|
||||
) as gzip_f:
|
||||
|
@ -105,21 +144,6 @@ def main():
|
|||
break
|
||||
cmd.wait(5)
|
||||
|
||||
print("Looking up the image id")
|
||||
image_id = subprocess.check_output(
|
||||
[
|
||||
args.runtime,
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
TAG,
|
||||
],
|
||||
text=True,
|
||||
)
|
||||
with open("share/image-id.txt", "w") as f:
|
||||
f.write(image_id)
|
||||
|
||||
|
||||
class ContainerPipDependencies:
|
||||
"""Generates PIP dependencies within container"""
|
||||
|
|
|
@ -17,22 +17,23 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd
|
|||
REM verify the signature of dangerzone-cli.exe
|
||||
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
|
||||
|
||||
REM build the wix file
|
||||
python install\windows\build-wxs.py > build\Dangerzone.wxs
|
||||
REM build the wxs file
|
||||
python install\windows\build-wxs.py
|
||||
|
||||
REM build the msi package
|
||||
cd build
|
||||
candle.exe Dangerzone.wxs
|
||||
light.exe -ext WixUIExtension Dangerzone.wixobj
|
||||
wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi
|
||||
|
||||
REM validate Dangerzone.msi
|
||||
wix msi validate Dangerzone.msi
|
||||
|
||||
REM code sign Dangerzone.msi
|
||||
insignia.exe -im Dangerzone.msi
|
||||
signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi
|
||||
|
||||
REM verify the signature of Dangerzone.msi
|
||||
signtool.exe verify /pa Dangerzone.msi
|
||||
|
||||
REM moving Dangerzone.msi to dist
|
||||
REM move Dangerzone.msi to dist
|
||||
cd ..
|
||||
mkdir dist
|
||||
move build\Dangerzone.msi dist
|
||||
|
|
|
@ -4,114 +4,75 @@ import uuid
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def build_data(dirname, dir_prefix, id_, name):
|
||||
def build_data(base_path, path_prefix, dir_id, dir_name):
|
||||
data = {
|
||||
"id": id_,
|
||||
"name": name,
|
||||
"directory_name": dir_name,
|
||||
"directory_id": dir_id,
|
||||
"files": [],
|
||||
"dirs": [],
|
||||
}
|
||||
|
||||
for basename in os.listdir(dirname):
|
||||
filename = os.path.join(dirname, basename)
|
||||
if os.path.isfile(filename):
|
||||
data["files"].append(os.path.join(dir_prefix, basename))
|
||||
elif os.path.isdir(filename):
|
||||
if id_ == "INSTALLDIR":
|
||||
id_prefix = "Folder"
|
||||
if dir_id == "INSTALLFOLDER":
|
||||
data["component_id"] = "ApplicationFiles"
|
||||
else:
|
||||
data["component_id"] = "Component" + dir_id
|
||||
data["component_guid"] = str(uuid.uuid4()).upper()
|
||||
|
||||
for entry in os.listdir(base_path):
|
||||
entry_path = os.path.join(base_path, entry)
|
||||
if os.path.isfile(entry_path):
|
||||
data["files"].append(os.path.join(path_prefix, entry))
|
||||
elif os.path.isdir(entry_path):
|
||||
if dir_id == "INSTALLFOLDER":
|
||||
next_dir_prefix = "Folder"
|
||||
else:
|
||||
id_prefix = id_
|
||||
next_dir_prefix = dir_id
|
||||
|
||||
# Skip lib/PySide6/examples folder due to ilegal file names
|
||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname:
|
||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path:
|
||||
continue
|
||||
|
||||
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
|
||||
# XXX Since we're not using Qml it should be no problem
|
||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname:
|
||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path:
|
||||
continue
|
||||
|
||||
id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
|
||||
data["dirs"].append(
|
||||
build_data(
|
||||
os.path.join(dirname, basename),
|
||||
os.path.join(dir_prefix, basename),
|
||||
id_value,
|
||||
basename,
|
||||
)
|
||||
next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_")
|
||||
subdata = build_data(
|
||||
os.path.join(base_path, entry),
|
||||
os.path.join(path_prefix, entry),
|
||||
next_dir_id,
|
||||
entry,
|
||||
)
|
||||
|
||||
if len(data["files"]) > 0:
|
||||
if id_ == "INSTALLDIR":
|
||||
data["component_id"] = "ApplicationFiles"
|
||||
else:
|
||||
data["component_id"] = "FolderComponent" + id_[len("Folder") :]
|
||||
data["component_guid"] = str(uuid.uuid4())
|
||||
# Add the subdirectory only if it contains files or subdirectories
|
||||
if subdata["files"] or subdata["dirs"]:
|
||||
data["dirs"].append(subdata)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def build_dir_xml(root, data):
|
||||
def build_directory_xml(root, data):
|
||||
attrs = {}
|
||||
if "id" in data:
|
||||
attrs["Id"] = data["id"]
|
||||
if "name" in data:
|
||||
attrs["Name"] = data["name"]
|
||||
el = ET.SubElement(root, "Directory", attrs)
|
||||
attrs["Id"] = data["directory_id"]
|
||||
attrs["Name"] = data["directory_name"]
|
||||
directory_el = ET.SubElement(root, "Directory", attrs)
|
||||
for subdata in data["dirs"]:
|
||||
build_dir_xml(el, subdata)
|
||||
|
||||
# If this is the ProgramMenuFolder, add the menu component
|
||||
if "id" in data and data["id"] == "ProgramMenuFolder":
|
||||
component_el = ET.SubElement(
|
||||
el,
|
||||
"Component",
|
||||
Id="ApplicationShortcuts",
|
||||
Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc",
|
||||
)
|
||||
ET.SubElement(
|
||||
component_el,
|
||||
"Shortcut",
|
||||
Id="ApplicationShortcut1",
|
||||
Name="Dangerzone",
|
||||
Description="Dangerzone",
|
||||
Target="[INSTALLDIR]dangerzone.exe",
|
||||
WorkingDirectory="INSTALLDIR",
|
||||
)
|
||||
ET.SubElement(
|
||||
component_el,
|
||||
"RegistryValue",
|
||||
Root="HKCU",
|
||||
Key="Software\Freedom of the Press Foundation\Dangerzone",
|
||||
Name="installed",
|
||||
Type="integer",
|
||||
Value="1",
|
||||
KeyPath="yes",
|
||||
)
|
||||
build_directory_xml(directory_el, subdata)
|
||||
|
||||
|
||||
def build_components_xml(root, data):
|
||||
component_ids = []
|
||||
if "component_id" in data:
|
||||
component_ids.append(data["component_id"])
|
||||
|
||||
component_el = ET.SubElement(
|
||||
root,
|
||||
"Component",
|
||||
Id=data["component_id"],
|
||||
Guid=data["component_guid"],
|
||||
Directory=data["directory_id"],
|
||||
)
|
||||
for filename in data["files"]:
|
||||
ET.SubElement(component_el, "File", Source=filename)
|
||||
for subdata in data["dirs"]:
|
||||
if "component_guid" in subdata:
|
||||
dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"])
|
||||
component_el = ET.SubElement(
|
||||
dir_ref_el,
|
||||
"Component",
|
||||
Id=subdata["component_id"],
|
||||
Guid=subdata["component_guid"],
|
||||
)
|
||||
for filename in subdata["files"]:
|
||||
file_el = ET.SubElement(
|
||||
component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex
|
||||
)
|
||||
|
||||
component_ids += build_components_xml(root, subdata)
|
||||
|
||||
return component_ids
|
||||
build_components_xml(root, subdata)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -125,120 +86,188 @@ def main():
|
|||
# -rc markers.
|
||||
version = f.read().strip().split("-")[0]
|
||||
|
||||
dist_dir = os.path.join(
|
||||
build_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||
"build",
|
||||
"exe.win-amd64-3.12",
|
||||
)
|
||||
|
||||
cx_freeze_dir = "exe.win-amd64-3.12"
|
||||
|
||||
dist_dir = os.path.join(build_dir, cx_freeze_dir)
|
||||
|
||||
if not os.path.exists(dist_dir):
|
||||
print("You must build the dangerzone binary before running this")
|
||||
return
|
||||
|
||||
data = {
|
||||
"id": "TARGETDIR",
|
||||
"name": "SourceDir",
|
||||
"dirs": [
|
||||
{
|
||||
"id": "ProgramFilesFolder",
|
||||
"dirs": [],
|
||||
},
|
||||
{
|
||||
"id": "ProgramMenuFolder",
|
||||
"dirs": [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
data["dirs"][0]["dirs"].append(
|
||||
build_data(
|
||||
dist_dir,
|
||||
"exe.win-amd64-3.12",
|
||||
"INSTALLDIR",
|
||||
"Dangerzone",
|
||||
)
|
||||
# Prepare data for WiX file harvesting from the output of cx_Freeze
|
||||
data = build_data(
|
||||
dist_dir,
|
||||
cx_freeze_dir,
|
||||
"INSTALLFOLDER",
|
||||
"Dangerzone",
|
||||
)
|
||||
|
||||
root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
|
||||
product_el = ET.SubElement(
|
||||
root_el,
|
||||
"Product",
|
||||
# Add the Wix root element
|
||||
wix_el = ET.Element(
|
||||
"Wix",
|
||||
{
|
||||
"xmlns": "http://wixtoolset.org/schemas/v4/wxs",
|
||||
"xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui",
|
||||
},
|
||||
)
|
||||
|
||||
# Add the Package element
|
||||
package_el = ET.SubElement(
|
||||
wix_el,
|
||||
"Package",
|
||||
Name="Dangerzone",
|
||||
Manufacturer="Freedom of the Press Foundation",
|
||||
Id="*",
|
||||
UpgradeCode="$(var.ProductUpgradeCode)",
|
||||
UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576",
|
||||
Language="1033",
|
||||
Codepage="1252",
|
||||
Version="$(var.ProductVersion)",
|
||||
)
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
"Package",
|
||||
Id="*",
|
||||
Keywords="Installer",
|
||||
Description="Dangerzone $(var.ProductVersion) Installer",
|
||||
Manufacturer="Freedom of the Press Foundation",
|
||||
InstallerVersion="100",
|
||||
Languages="1033",
|
||||
Compressed="yes",
|
||||
SummaryCodepage="1252",
|
||||
Codepage="1252",
|
||||
Version=version,
|
||||
)
|
||||
ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
|
||||
ET.SubElement(
|
||||
product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
|
||||
package_el,
|
||||
"SummaryInformation",
|
||||
Keywords="Installer",
|
||||
Description="Dangerzone " + version + " Installer",
|
||||
Codepage="1252",
|
||||
)
|
||||
ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
|
||||
ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes")
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
|
||||
)
|
||||
ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
|
||||
ET.SubElement(
|
||||
package_el,
|
||||
"Property",
|
||||
Id="ARPHELPLINK",
|
||||
Value="https://dangerzone.rocks",
|
||||
)
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
package_el,
|
||||
"Property",
|
||||
Id="ARPURLINFOABOUT",
|
||||
Value="https://freedom.press",
|
||||
)
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
"Property",
|
||||
Id="WIXUI_INSTALLDIR",
|
||||
Value="INSTALLDIR",
|
||||
package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER"
|
||||
)
|
||||
ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir")
|
||||
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
|
||||
ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText")
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
package_el,
|
||||
"WixVariable",
|
||||
Id="WixUILicenseRtf",
|
||||
Value="..\\install\\windows\\license.rtf",
|
||||
)
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
package_el,
|
||||
"WixVariable",
|
||||
Id="WixUIDialogBmp",
|
||||
Value="..\\install\\windows\\dialog.bmp",
|
||||
)
|
||||
ET.SubElement(
|
||||
product_el,
|
||||
package_el,
|
||||
"MajorUpgrade",
|
||||
AllowSameVersionUpgrades="yes",
|
||||
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
|
||||
)
|
||||
|
||||
build_dir_xml(product_el, data)
|
||||
component_ids = build_components_xml(product_el, data)
|
||||
# Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous
|
||||
# version of Dangerzone is not uninstalled during the upgrade by checking if the older installation
|
||||
# exists in "C:\Program Files (x86)\Dangerzone".
|
||||
#
|
||||
# Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location
|
||||
# during install by checking if the registry key for it exists.
|
||||
#
|
||||
# Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch.
|
||||
# In this case the installer errors until Dangerzone 0.8.0 is uninstalled again
|
||||
#
|
||||
# TODO: Revert this once we are reasonably certain there aren't too many affected Dangerzone installations.
|
||||
find_old_el = ET.SubElement(package_el, "Property", Id="OLDDANGERZONEFOUND")
|
||||
directory_search_el = ET.SubElement(
|
||||
find_old_el,
|
||||
"DirectorySearch",
|
||||
Id="dangerzone_install_folder",
|
||||
Path="C:\\Program Files (x86)\\Dangerzone",
|
||||
)
|
||||
ET.SubElement(directory_search_el, "FileSearch", Name="dangerzone.exe")
|
||||
registry_search_el = ET.SubElement(package_el, "Property", Id="DANGERZONE080FOUND")
|
||||
ET.SubElement(
|
||||
registry_search_el,
|
||||
"RegistrySearch",
|
||||
Root="HKLM",
|
||||
Key="SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{03C2D2B2-9955-4AED-831F-DA4E67FC0FDB}",
|
||||
Name="DisplayName",
|
||||
Type="raw",
|
||||
)
|
||||
ET.SubElement(
|
||||
package_el,
|
||||
"Launch",
|
||||
Condition="NOT OLDDANGERZONEFOUND AND NOT DANGERZONE080FOUND",
|
||||
Message="A previous version of [ProductName] is already installed. Please uninstall it from Programs and Features before proceeding with the installation.",
|
||||
)
|
||||
|
||||
feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
|
||||
for component_id in component_ids:
|
||||
ET.SubElement(feature_el, "ComponentRef", Id=component_id)
|
||||
# Add the ProgramMenuFolder StandardDirectory
|
||||
programmenufolder_el = ET.SubElement(
|
||||
package_el,
|
||||
"StandardDirectory",
|
||||
Id="ProgramMenuFolder",
|
||||
)
|
||||
# Add a shortcut for Dangerzone in the Start menu
|
||||
shortcut_el = ET.SubElement(
|
||||
programmenufolder_el,
|
||||
"Component",
|
||||
Id="ApplicationShortcuts",
|
||||
Guid="539E7DE8-A124-4C09-AA55-0DD516AAD7BC",
|
||||
)
|
||||
ET.SubElement(
|
||||
shortcut_el,
|
||||
"Shortcut",
|
||||
Id="DangerzoneStartMenuShortcut",
|
||||
Name="Dangerzone",
|
||||
Description="Dangerzone",
|
||||
Target="[INSTALLFOLDER]dangerzone.exe",
|
||||
WorkingDirectory="INSTALLFOLDER",
|
||||
)
|
||||
ET.SubElement(
|
||||
shortcut_el,
|
||||
"RegistryValue",
|
||||
Root="HKCU",
|
||||
Key="Software\\Freedom of the Press Foundation\\Dangerzone",
|
||||
Name="installed",
|
||||
Type="integer",
|
||||
Value="1",
|
||||
KeyPath="yes",
|
||||
)
|
||||
|
||||
# Add the ProgramFilesFolder StandardDirectory
|
||||
programfilesfolder_el = ET.SubElement(
|
||||
package_el,
|
||||
"StandardDirectory",
|
||||
Id="ProgramFiles64Folder",
|
||||
)
|
||||
|
||||
# Create the directory structure for the installed product
|
||||
build_directory_xml(programfilesfolder_el, data)
|
||||
|
||||
# Create a component group for application components
|
||||
applicationcomponents_el = ET.SubElement(
|
||||
package_el, "ComponentGroup", Id="ApplicationComponents"
|
||||
)
|
||||
# Populate the application components group with components for the installed package
|
||||
build_components_xml(applicationcomponents_el, data)
|
||||
|
||||
# Add the Feature element
|
||||
feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1")
|
||||
ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents")
|
||||
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
|
||||
|
||||
print('<?xml version="1.0" encoding="windows-1252"?>')
|
||||
print(f'<?define ProductVersion = "{version}"?>')
|
||||
print('<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>')
|
||||
ET.indent(root_el)
|
||||
print(ET.tostring(root_el).decode())
|
||||
ET.indent(wix_el, space=" ")
|
||||
|
||||
with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file:
|
||||
wxs_file.write(ET.tostring(wix_el).decode())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
537
poetry.lock
generated
537
poetry.lock
generated
|
@ -13,24 +13,24 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.6.2.post1"
|
||||
version = "4.7.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"},
|
||||
{file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"},
|
||||
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
|
||||
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
|
@ -44,61 +44,15 @@ files = [
|
|||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
|
||||
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
|
||||
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
|
||||
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
|
||||
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
|
||||
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
|
||||
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
|
||||
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
|
||||
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
|
||||
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
|
||||
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
|
||||
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
|
||||
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
|
||||
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
|
||||
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
|
||||
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
|
||||
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
|
||||
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
version = "2024.12.14"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
|
||||
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -229,6 +183,17 @@ files = [
|
|||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "cloudpickle"
|
||||
version = "3.1.0"
|
||||
description = "Pickler class to extend the standard pickle.Pickler functionality"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"},
|
||||
{file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
|
@ -242,73 +207,73 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.6.7"
|
||||
version = "7.6.9"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"},
|
||||
{file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"},
|
||||
{file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"},
|
||||
{file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"},
|
||||
{file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"},
|
||||
{file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"},
|
||||
{file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"},
|
||||
{file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"},
|
||||
{file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
|
||||
{file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
|
||||
{file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
|
||||
{file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
|
||||
{file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
|
||||
{file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
|
||||
{file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
|
||||
{file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
|
||||
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -319,53 +284,61 @@ toml = ["tomli"]
|
|||
|
||||
[[package]]
|
||||
name = "cx-freeze"
|
||||
version = "7.2.5"
|
||||
version = "7.2.7"
|
||||
description = "Create standalone executables from Python scripts"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2fee88d083d25ff8e25d8c8b019471d84156579983c09386f858c5993701165"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e964e72765e29def25c6ecb55d7b12c6f82a65ef3bea6cdfd29ca11e8ad7bf97"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce6812cf281312294de8c6d3f6c477fb7aef3882295c89f0b1a313cbdc460092"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e45fd912d979acf9764672d909a17eec19c43a96044c5b91ba17d812e2e52074"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4f3327800b988bf9d54636ff47423e986b649fa6f0e26e5033dfa4d7c4a86def"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:eacd1b1d8b0428d3a70428a4f5671f1aab61968bee23afc804af8c22ba21c1dd"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1127748fe62a888af8689c005fc8dabad586441d51370c0c49041d66b394c507"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-win32.whl", hash = "sha256:bab38d4ea9d79c1c31d8e2d93f47560dd32bf84251d907f0333178c25ae5dff3"},
|
||||
{file = "cx_Freeze-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:2ad91b52cf41e4b097967861b0b1d6698ddd4f3933ae06707477331467510825"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f69c499b37baed9ca6fe9bb89eed73514d721ae55fe262a3b337bf9322f794c"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b12e1daf5408986906fd5040505b1be75b86a66bea8af3e164c4d3b6a0ba"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:152c13fe9896d246a68fc32987e60afef46e6c8b2386f13265a8c87d54953575"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:694887e5439ecbb9d21b96ebb89ee8f4f9ad21e0fb6ee86a60d0f823b1358182"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e972d72e43142490d3389e843c24af50ddf031150fdf26be8a70e26c7317244"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3290c6c155fc33c81fa557bae5d648441066fb55a9ff41558b3ea2b085ba3e5b"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:431771e5a199e8cffaa7a448212a06b04316b3a268a7e6bbbc509ed62339cf4f"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c9933f8346416b23a6d91ec27156aa1d67a0225e69c6602ca42b7d84f65c12c5"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1843a959e76f407ca86992517236dd11d2fd3568b7ae87396102d4964dc899be"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-win32.whl", hash = "sha256:4419722a93a1cf2fef45c1096ea5e1ca402bfc59c0adcb326776561881292cb1"},
|
||||
{file = "cx_Freeze-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:975a25e9cfda1c4c956bc20e8f712416c9b24a8dbe89f56c3f1c20c6e1c77006"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fcd644ac4e8938b4984a1f9af10f29bec9ddc47390f608d1cfd608d0fa3d38"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de019dd0d537eac05d6bd40452d6e5b83abe3b141d9ae2c757eb68b82760093f"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fb4b5bb07773fed714e59fc994a40cdc585d9e86bf01a87053906aacbe02aea"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d0b660d888b18562aa06181f957ae5fcea62767df09b8fbe4613f079343d3fd"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99116e7cb03d1c648c6351e7e0b9c2ed24be614118a4c52f00f8e34afa61d634"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b731c9b6c217d1b22d24a71692f481c26ea6ce14dc6d5f7b18cbd89e0f942e"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a37384ca157c7f4f527d8075853622fab0870fa3481a00394704e1599ad00486"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4cad6a0cdf1e198674c3fc5f84027edd1dd3dc3a9de94a4c9573c9afb7a66e7"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:389f2ff47a624016e5410bfe4906357554e6f191000e658f06e87c51d649cd3b"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-win32.whl", hash = "sha256:c28960966ae87b53a07519d3c1d64735f67d5c2c866d574657f26a97b033df0d"},
|
||||
{file = "cx_Freeze-7.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:e7271051c32b0afd0f504d16eb79288a4abc6d118eb7939ef38cabab68b22b9a"},
|
||||
{file = "cx_Freeze-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c8bbe721146a611c4c37847c6ecb61da3b3404c150a7c19b16a520004090ca1d"},
|
||||
{file = "cx_Freeze-7.2.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31fc0315ccd082d9c14ffc15efd95e86d3cdfe27906c6d8777a9cca7a19823a4"},
|
||||
{file = "cx_Freeze-7.2.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4c3419d957725dddd1b9c5adbbe004f5b0bd84888dfe362997db2fa754241f20"},
|
||||
{file = "cx_Freeze-7.2.5-cp38-cp38-win32.whl", hash = "sha256:2fbf6cf8198f23da8b85d8bdc3c680402465433f402cd1bedc8a7c8681d0bda0"},
|
||||
{file = "cx_Freeze-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:d3f9c7e81b970f8d9e0d3527232cf8e4d486fc8768f185d787f21c3045c3ba24"},
|
||||
{file = "cx_Freeze-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:001a8d61e1b93425f3fee84e2cba0ddad07897da7777e576124b674f7952396c"},
|
||||
{file = "cx_Freeze-7.2.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eaf9aaa15f472cdb05a10d20fc4e8239c3d02348945b09ad10bec777e35db8a"},
|
||||
{file = "cx_Freeze-7.2.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:03b6cb977a17134c8470f2e626cc52f026d590e900fed75e205310d61515c00d"},
|
||||
{file = "cx_Freeze-7.2.5-cp39-cp39-win32.whl", hash = "sha256:0b9a1e349e84ef9edc035451714ccd5fabe829336026d4073c987b15ccf63246"},
|
||||
{file = "cx_Freeze-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:bcb4077f531a3559f89394b5f1ddba4e92033bd79653bd09854b705389ebdc07"},
|
||||
{file = "cx_freeze-7.2.5.tar.gz", hash = "sha256:63f9b745b8a84a1c3ce986d089f3d750ec65b9ff385ed64308c073a821d81eef"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fd56b5bfc37d38cc64700b8f17ff76632dcb03f739ca5b0f6038f8ab39e136d"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e065227b26477bfe8e0a2adc1e7b6bd318c7e0690b60bf1b2370598e5696c598"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da55ecd56e5e05f9485151d9af0abf18150f86ce4c2734d9a71fabba3f3cd524"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77866e408abe64869361590dc6aba8d57d7e7ab4693c06f1334eabc5d7155361"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9b1a27ce838d4c262264e3aa6a5e7c5d4468a870ee873e3a49269a8752ab61fa"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:97a227c567e86b7d3b83ab5f96f96070b2396943c47d7c64d643770944acd5c5"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:839140debb42792be2ff6f0e8b261f3e230d0dfe71164c06ee8fa1bbaf2ff1a9"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-win32.whl", hash = "sha256:c34a6e85897f5cb1be84a204feef564adb6f1c753626bf0cf7713a8c4809ed27"},
|
||||
{file = "cx_Freeze-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:5ea3f05d31a7432b0516a58e4b7301277c0a5ac693597d531d9ca913d79169ce"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65e900cec673dc9bf6b8e21a760b3964b5b20b2586f274184c4c474c78e343f3"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c9119b8dbe93b362d4658ed817e28d9d2fd9c29d5ade565ca23cd0e049440a8"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a26b65bd4e2414bbab84307a906ea207de3c1f3198bc9f6286a81597ddebe38"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa7e75e8dd4ffce44465141c2857fcf5fb5a31dfbcabbbee9929b55e79f051e2"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b1fe00f689a0c06197bef07ba3dce985d768c405e0042dfa796bbafa53d6b4"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34d0ec5a41d59c55878711cf3812f46444d71f911c8ed1de467591c36aed9d6d"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3131a443473c21e019317419a9b3a82b2ae1cec5c76bce6c9e2f36f41803720a"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:93179c236c3e1e64b6b8101d9012a8084df74736ed5cf10047721b4c7c2076ef"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:05dac0676586eed96fbde187fd63ace05ec0c4462ec64367a7d47af0d266008e"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-win32.whl", hash = "sha256:909784281dad31c92c0f402894d99a6188bb81f22b593d9d55fc437d54ab35e0"},
|
||||
{file = "cx_Freeze-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:8fff35321128f680a825d28074edd14b81bdd68b98128d4fa655c6c8901de3ce"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6e22b449cf401a7d152e97a5feec71b492083067ca0336b74eb988fbfebfb3a6"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8611df6e740a7980145f2d6a3d9f9161fe4d6a12c0055da52ccccffe7d77d470"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66fa38df2423a3ded9c59a6a089293db3b7a6d3493a701b31d24f97cd29f2143"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41c6fd1e77a733fc1a0f4e3678e136726cc45a11b37ff5722ac18afdad703934"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d00dc0bf7ed9a30491ec34bfb8f4347446afae506febc2afc2531ecbf9db94bf"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a27b2a1925138e122435ef3d52c281d185a57d1f67e08232ea61be83174cd0"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4ef57f4102a658e10704619294180b17a2d6c6595ff3753c9557b05e6a928c7f"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:caa77cbbcb0665c6e5a5b2e31a13b63e99201a8f258244bcfc1aed737484070f"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58280540d54fd248120fa9acb1ac9d229fa9b508297f855da05e3f4ed9255a2e"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-win32.whl", hash = "sha256:344276a8354e4688c20dd3cf70c143d7666f1b93f56532ba9549718c2fac4e56"},
|
||||
{file = "cx_Freeze-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9a727914e702d98648def98626b13864413a4a5d52b8bd7f6f0d8ffaf7b9207f"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca1e82671db7ba48993a52bc351e34115eb2c5c91d8549d3ab60d717b941e6a3"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58dbe6c30ab6583cf52f1b38a2f0901a57c80dee84f01271c9e6cfe3b7476ed0"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb6725bc74e6e501b3289f6b58604bec94e0790f7bd5112e6f35c3e2638c6eb"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dd3da95443bf0bc7eb1df23f55a35f42449f1d211c5edde59a2bd811030141c6"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0064f3de0dea0ee3a89cc864b13b1bd0382d163e28e23518e7cce5efd94a87a6"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca1ae3b12abc6472f9e9ab6e66265941e044770f29bc64db7c545eff5611af68"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-win32.whl", hash = "sha256:546192ab868b9a4123c252d2fdd0af349b71242ae9706fff201b1c3dabe928c0"},
|
||||
{file = "cx_Freeze-7.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:a1de14dc9960ae4065d053bcd5a801ef51761c7f92998f5a42810098eb337102"},
|
||||
{file = "cx_Freeze-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e36fe7ad2974db158293672c9f1ae750e9e08fda56b6281760a23b77770bd240"},
|
||||
{file = "cx_Freeze-7.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6523d10c7066de2d17f60daac8256169e330f207b786ee1d071273b4a1e7c1fc"},
|
||||
{file = "cx_Freeze-7.2.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ff3587628a73d46e1d9d7c8d7ed6e6fe1b4c7aebbeb034b923b6fabdbbe5149f"},
|
||||
{file = "cx_Freeze-7.2.7-cp38-cp38-win32.whl", hash = "sha256:9859ba95a103f3b684553e4728fece150cf42b91c9f28873c661a6a3cbc3e226"},
|
||||
{file = "cx_Freeze-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:c359c8333ea798373428f87fc6a56c6d89bdba6f974a55b7c3d6259342ff7036"},
|
||||
{file = "cx_Freeze-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:638f0107a95c639633cff2e84f9ec55b6687612a460a5f42e1ffc36a8c30e51d"},
|
||||
{file = "cx_Freeze-7.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e324896652b44f06e8fecd0da706bad37311a639a1128bbd41cb19cf3069d4f1"},
|
||||
{file = "cx_Freeze-7.2.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fc4ab1bd795dc7c985418fff3431bd6a3712df2e84e2f98d8c65d3a748fb5e14"},
|
||||
{file = "cx_Freeze-7.2.7-cp39-cp39-win32.whl", hash = "sha256:0c8512d24a7dcf3b35bd1e17f9e2d6f6a75e227aff243cd00c1aa4668b64ec01"},
|
||||
{file = "cx_Freeze-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:3b1cdb80b75e142a3e85e71721edd8c423ff41a974dfe9436a09593129446ebf"},
|
||||
{file = "cx_freeze-7.2.7.tar.gz", hash = "sha256:c4d43a4e5357352ac5a89203fda3a8cfadf033d43682f73abbefd3860ce50fb4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -378,7 +351,7 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
|
|||
typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["bump-my-version (==0.26.1)", "cibuildwheel (==2.21.1)", "pre-commit (>=3.5.0,<=3.8.0)"]
|
||||
dev = ["build (>=1.2.0)", "bump-my-version (==0.26.1)", "cibuildwheel (==2.22.0)", "pre-commit (>=3.5.0,<=3.8.0)"]
|
||||
doc = ["furo (==2024.8.6)", "myst-parser (>=3.0.1,<=4.0.0)", "sphinx (>=7.1.2,<8)", "sphinx-new-tab-link (==0.6.0)", "sphinx-tabs (==3.4.5)"]
|
||||
test = ["coverage (==7.6.1)", "pluggy (==1.5.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-datafiles (==3.0.0)", "pytest-mock (==3.14.0)", "pytest-timeout (==2.3.1)", "pytest-xdist[psutil] (==3.6.1)"]
|
||||
|
||||
|
@ -412,6 +385,24 @@ files = [
|
|||
{file = "cx_logging-3.2.1.tar.gz", hash = "sha256:812665ae5012680a6fe47095c3772bce638e47cf05b2c3483db3bdbe6b06da44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doit"
|
||||
version = "0.36.0"
|
||||
description = "doit - Automation Tool"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"},
|
||||
{file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cloudpickle = "*"
|
||||
importlib-metadata = ">=4.4"
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
|
@ -531,20 +522,6 @@ files = [
|
|||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.13.2"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "lief"
|
||||
version = "0.15.1"
|
||||
|
@ -709,33 +686,6 @@ files = [
|
|||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
|
||||
type = ["mypy (>=1.11.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
|
@ -819,54 +769,54 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "pyside6"
|
||||
version = "6.8.0.2"
|
||||
version = "6.8.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "PySide6-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:cecc6ce1da6cb04542ff5a0887734f63e6ecf54258d1786285b9c7904abd9b01"},
|
||||
{file = "PySide6-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3258f3c63dc5053b8d5b8d2588caca8bb3a36e2f74413511e4676df0e73b6f1e"},
|
||||
{file = "PySide6-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:6a25cf784f978fa2a23b4d089970b27ebe14d26adcaf38b2819cb04483de4ce9"},
|
||||
{file = "PySide6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:3e8fffca9a934e30c07c3f34bb572f84bfcf02385acbc715e65fbdd9746ecc2b"},
|
||||
{file = "PySide6-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:6d1fd95651cdbdea741af21e155350986eca31ff015fc4c721ce01c2a110a4cc"},
|
||||
{file = "PySide6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d6adc5d53313249bbe02edb673877c1d437e215d71e88da78412520653f5c9f"},
|
||||
{file = "PySide6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:ddeeaeca8ebd0ddb1ded30dd33e9240a40f330cc91832de346ba6c9d0cd1253e"},
|
||||
{file = "PySide6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:866eeaca3ffead6b9d30fa3ed395d5624da0246d7586c8b8207e77ac65d82458"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PySide6-Addons = "6.8.0.2"
|
||||
PySide6-Essentials = "6.8.0.2"
|
||||
shiboken6 = "6.8.0.2"
|
||||
PySide6-Addons = "6.8.1"
|
||||
PySide6-Essentials = "6.8.1"
|
||||
shiboken6 = "6.8.1"
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-addons"
|
||||
version = "6.8.0.2"
|
||||
version = "6.8.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:30c9ca570dd18ffbfd34ee95e0a319c34313a80425c4011d6ccc9f4cca0dc4c8"},
|
||||
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:754a9822ab2dc313f9998edef69d8a12bc9fd61727543f8d30806ed272ae1e52"},
|
||||
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:553f3fa412f423929b5cd8b7d43fd5f02161851f10a438174a198b0f1a044df7"},
|
||||
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:ae4377a3e10fe720a9119677b31d8de13e2a5221c06b332df045af002f5f4c3d"},
|
||||
{file = "PySide6_Addons-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:879c12346b4b76f5d5ee6499d8ca53b5666c0c998b8fdf8780f08f69ea95d6f9"},
|
||||
{file = "PySide6_Addons-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f80cc03c1ac54132c6f800aa461dced64acd7d1646898db164ccb56fe3c23dd4"},
|
||||
{file = "PySide6_Addons-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:570a25016d80046274f454ed0bb06734f478ce6c21be5dec62b624773fc7504e"},
|
||||
{file = "PySide6_Addons-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:d7c8c1e89ee0db84631d5b8fdb9129d9d2a0ffb3b4cb2f5192dc8367dd980db4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PySide6-Essentials = "6.8.0.2"
|
||||
shiboken6 = "6.8.0.2"
|
||||
PySide6-Essentials = "6.8.1"
|
||||
shiboken6 = "6.8.1"
|
||||
|
||||
[[package]]
|
||||
name = "pyside6-essentials"
|
||||
version = "6.8.0.2"
|
||||
version = "6.8.1"
|
||||
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3df4ed75bbb74d74ac338b330819b1a272e7f5cec206765c7176a197c8bc9c79"},
|
||||
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7df6d6c1da4858dbdea77c74d7270d9c68e8d1bbe3362892abd1a5ade3815a50"},
|
||||
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:cf490145d18812a6cff48b0b0afb0bfaf7066744bfbd09eb071c3323f1d6d00d"},
|
||||
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:d2f029b8c9f0106f57b26aa8c435435d7f509c80525075343e07177b283f862e"},
|
||||
{file = "PySide6_Essentials-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:bd05155245e3cd1572e68d72772e78fadfd713575bbfdd2c5e060d5278e390e9"},
|
||||
{file = "PySide6_Essentials-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f600b149e65b57acd6a444edb17615adc42cc2491548ae443ccb574036d86b1"},
|
||||
{file = "PySide6_Essentials-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bf8a3c9ee0b997eb18fb00cb09aacaa28b8a51ce3c295a252cc594c5530aba56"},
|
||||
{file = "PySide6_Essentials-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:d5ed4ddb149f36d65bc49ae4260b2d213ee88b2d9a309012ae27f38158c2d1b6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
shiboken6 = "6.8.0.2"
|
||||
shiboken6 = "6.8.1"
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
|
@ -1037,37 +987,64 @@ urllib3 = ">=1.21.1,<3"
|
|||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
|
||||
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
|
||||
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
|
||||
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
|
||||
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
|
||||
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
|
||||
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
|
||||
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
|
||||
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "75.5.0"
|
||||
version = "75.6.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"},
|
||||
{file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"},
|
||||
{file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
|
||||
{file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"]
|
||||
core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
|
||||
type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
|
||||
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "shiboken6"
|
||||
version = "6.8.0.2"
|
||||
version = "6.8.1"
|
||||
description = "Python/C++ bindings helper module"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
files = [
|
||||
{file = "shiboken6-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:9019e1fcfeed8bb350222e981748ef05a2fec11e31ddf616657be702f0b7a468"},
|
||||
{file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fa7d411c3c67b4296847b3f5f572268e219d947d029ff9d8bce72fe6982d92bc"},
|
||||
{file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:1aaa8b7f9138818322ef029b2c487d1c6e00dc3f53084e62e1d11bdea47e47c2"},
|
||||
{file = "shiboken6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:b11e750e696bb565d897e0f5836710edfb86bd355f87b09988bd31b2aad404d3"},
|
||||
{file = "shiboken6-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:9a2f51d1ddd3b6d193a0f0fdc09f8d41f2092bc664723c9b9efc1056660d0608"},
|
||||
{file = "shiboken6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1dc4c1976809b0e68872bb98474cccd590455bdcd015f0e0639907e94af27b6a"},
|
||||
{file = "shiboken6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:ab5b60602ca6227103138aae89c4f5df3b1b8e249cbc8ec9e6e2a57f20ad9a91"},
|
||||
{file = "shiboken6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:3ea127fd72be113b73cacd70e06687ad6f83c1c888047833c7dcdd5cf8e7f586"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1094,26 +1071,93 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.1.0"
|
||||
version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
|
||||
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-colorama"
|
||||
version = "0.4.15.20240311"
|
||||
description = "Typing stubs for colorama"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a"},
|
||||
{file = "types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-docutils"
|
||||
version = "0.21.0.20241128"
|
||||
description = "Typing stubs for docutils"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039"},
|
||||
{file = "types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-markdown"
|
||||
version = "3.7.0.20240822"
|
||||
version = "3.7.0.20241204"
|
||||
description = "Typing stubs for Markdown"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Markdown-3.7.0.20240822.tar.gz", hash = "sha256:183557c9f4f865bdefd8f5f96a38145c31819271cde111d35557c3bd2069e78d"},
|
||||
{file = "types_Markdown-3.7.0.20240822-py3-none-any.whl", hash = "sha256:bec91c410aaf2470ffdb103e38438fbcc53689b00133f19e64869eb138432ad7"},
|
||||
{file = "types_Markdown-3.7.0.20241204-py3-none-any.whl", hash = "sha256:f96146c367ea9c82bfe9903559d72706555cc2a1a3474c58ebba03b418ab18da"},
|
||||
{file = "types_markdown-3.7.0.20241204.tar.gz", hash = "sha256:ecca2b25cd23163fd28ed5ba34d183d731da03e8a5ed3a20b60daded304c5410"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pygments"
|
||||
version = "2.18.0.20240506"
|
||||
description = "Typing stubs for Pygments"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Pygments-2.18.0.20240506.tar.gz", hash = "sha256:4b4c37812c87bbde687dbf27adf5bac593745a321e57f678dbc311571ba2ac9d"},
|
||||
{file = "types_Pygments-2.18.0.20240506-py3-none-any.whl", hash = "sha256:11c90bc1737c9af55e5569558b88df7c2233e12325cb516215f722271444e91d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
types-docutils = "*"
|
||||
types-setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pyside2"
|
||||
version = "5.15.2.1.7"
|
||||
|
@ -1139,6 +1183,17 @@ files = [
|
|||
[package.dependencies]
|
||||
urllib3 = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "75.6.0.20241126"
|
||||
description = "Typing stubs for setuptools"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"},
|
||||
{file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
|
@ -1189,4 +1244,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "5d1ff28aa04c3a814280e55c0b2a307efe5ca953cd4cb281056c35fd2e53fdf0"
|
||||
content-hash = "68663ce40ba8a7c7f7cc7868e5f771472555773ff2ef04dad7e0150218ca3eb0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[tool.poetry]
|
||||
[tool.poetry]
|
||||
name = "dangerzone"
|
||||
version = "0.8.0"
|
||||
description = "Take potentially dangerous PDFs, office documents, or images and convert them to safe PDFs"
|
||||
|
@ -34,14 +34,17 @@ setuptools = "*"
|
|||
cx_freeze = {version = "^7.2.5", platform = "win32"}
|
||||
pywin32 = {version = "*", platform = "win32"}
|
||||
pyinstaller = {version = "*", platform = "darwin"}
|
||||
doit = "^0.36.0"
|
||||
|
||||
# Dependencies required for linting the code.
|
||||
[tool.poetry.group.lint.dependencies]
|
||||
black = "*"
|
||||
isort = "*"
|
||||
click = "*" # Install click so mypy is able to reason about it.
|
||||
mypy = "*"
|
||||
ruff = "*"
|
||||
types-colorama = "*"
|
||||
types-PySide2 = "*"
|
||||
types-Markdown = "*"
|
||||
types-pygments = "*"
|
||||
types-requests = "*"
|
||||
|
||||
# Dependencies required for testing the code.
|
||||
|
@ -60,11 +63,19 @@ pymupdf = "1.24.11" # Last version to support python 3.8 (needed for Ubuntu Foca
|
|||
[tool.poetry.group.dev.dependencies]
|
||||
httpx = "^0.27.2"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
skip_gitignore = true
|
||||
# This is necessary due to https://github.com/PyCQA/isort/issues/1835
|
||||
follow_links = false
|
||||
[tool.doit]
|
||||
verbosity = 3
|
||||
|
||||
[tool.doit.tasks.build_image]
|
||||
# DO NOT change this to 'true' for release artifacts, else we risk building
|
||||
# images that are a few days behind. See also: docs/developer/doit.md
|
||||
use_cache = false
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# isort
|
||||
"I",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.2.0"]
|
||||
|
|
|
@ -33,17 +33,19 @@ def test_order_mime_handers() -> None:
|
|||
"LibreOffice",
|
||||
]
|
||||
|
||||
with mock.patch(
|
||||
"subprocess.check_output", return_value=b"libreoffice-draw.desktop"
|
||||
) as mock_default_mime_hander, mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["org.gnome.Evince.desktop"],
|
||||
["org.pwmt.zathura-pdf-mupdf.desktop"],
|
||||
["libreoffice-draw.desktop"],
|
||||
],
|
||||
) as mock_list, mock.patch(
|
||||
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
|
||||
with (
|
||||
mock.patch(
|
||||
"subprocess.check_output", return_value=b"libreoffice-draw.desktop"
|
||||
) as mock_default_mime_hander,
|
||||
mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["org.gnome.Evince.desktop"],
|
||||
["org.pwmt.zathura-pdf-mupdf.desktop"],
|
||||
["libreoffice-draw.desktop"],
|
||||
],
|
||||
) as mock_list,
|
||||
mock.patch("dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop),
|
||||
):
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
|
||||
|
@ -77,18 +79,20 @@ def test_mime_handers_succeeds_no_default_found() -> None:
|
|||
"LibreOffice",
|
||||
]
|
||||
|
||||
with mock.patch(
|
||||
"subprocess.check_output",
|
||||
side_effect=subprocess.CalledProcessError(1, "Oh no, xdg-mime error!)"),
|
||||
) as mock_default_mime_hander, mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["org.gnome.Evince.desktop"],
|
||||
["org.pwmt.zathura-pdf-mupdf.desktop"],
|
||||
["libreoffice-draw.desktop"],
|
||||
],
|
||||
) as mock_list, mock.patch(
|
||||
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
|
||||
with (
|
||||
mock.patch(
|
||||
"subprocess.check_output",
|
||||
side_effect=subprocess.CalledProcessError(1, "Oh no, xdg-mime error!)"),
|
||||
) as mock_default_mime_hander,
|
||||
mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["org.gnome.Evince.desktop"],
|
||||
["org.pwmt.zathura-pdf-mupdf.desktop"],
|
||||
["libreoffice-draw.desktop"],
|
||||
],
|
||||
) as mock_list,
|
||||
mock.patch("dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop),
|
||||
):
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
|
||||
|
@ -109,13 +113,16 @@ def test_malformed_desktop_entry_is_catched() -> None:
|
|||
mock_app = mock.MagicMock()
|
||||
dummy = mock.MagicMock()
|
||||
|
||||
with mock.patch("dangerzone.gui.logic.DesktopEntry") as mock_desktop, mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["malformed.desktop", "another.desktop"],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
with (
|
||||
mock.patch("dangerzone.gui.logic.DesktopEntry") as mock_desktop,
|
||||
mock.patch(
|
||||
"os.listdir",
|
||||
side_effect=[
|
||||
["malformed.desktop", "another.desktop"],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
),
|
||||
):
|
||||
mock_desktop.side_effect = ParsingError("Oh noes!", "malformed.desktop")
|
||||
DangerzoneGui(mock_app, dummy)
|
||||
|
|
|
@ -7,9 +7,9 @@ from typing import List
|
|||
|
||||
from pytest import MonkeyPatch, fixture
|
||||
from pytest_mock import MockerFixture
|
||||
from pytest_subprocess import FakeProcess
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from dangerzone import errors
|
||||
from dangerzone.document import Document
|
||||
from dangerzone.gui import MainWindow
|
||||
from dangerzone.gui import main_window as main_window_module
|
||||
|
@ -25,11 +25,8 @@ from dangerzone.gui.main_window import (
|
|||
WaitingWidgetContainer,
|
||||
)
|
||||
from dangerzone.gui.updater import UpdateReport, UpdaterThread
|
||||
from dangerzone.isolation_provider.container import (
|
||||
Container,
|
||||
NoContainerTechException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
from dangerzone.isolation_provider.container import Container
|
||||
from dangerzone.isolation_provider.dummy import Dummy
|
||||
|
||||
from .test_updater import assert_report_equal, default_updater_settings
|
||||
|
||||
|
@ -510,9 +507,9 @@ def test_not_available_container_tech_exception(
|
|||
) -> None:
|
||||
# Setup
|
||||
mock_app = mocker.MagicMock()
|
||||
dummy = mocker.MagicMock()
|
||||
|
||||
dummy.is_runtime_available.side_effect = NotAvailableContainerTechException(
|
||||
dummy = Dummy()
|
||||
fn = mocker.patch.object(dummy, "is_available")
|
||||
fn.side_effect = errors.NotAvailableContainerTechException(
|
||||
"podman", "podman image ls logs"
|
||||
)
|
||||
|
||||
|
@ -535,7 +532,7 @@ def test_no_container_tech_exception(qtbot: QtBot, mocker: MockerFixture) -> Non
|
|||
dummy = mocker.MagicMock()
|
||||
|
||||
# Raise
|
||||
dummy.is_runtime_available.side_effect = NoContainerTechException("podman")
|
||||
dummy.is_available.side_effect = errors.NoContainerTechException("podman")
|
||||
|
||||
dz = DangerzoneGui(mock_app, dummy)
|
||||
widget = WaitingWidgetContainer(dz)
|
||||
|
|
|
@ -4,12 +4,8 @@ import pytest
|
|||
from pytest_mock import MockerFixture
|
||||
from pytest_subprocess import FakeProcess
|
||||
|
||||
from dangerzone.isolation_provider.container import (
|
||||
Container,
|
||||
ImageInstallationException,
|
||||
ImageNotPresentException,
|
||||
NotAvailableContainerTechException,
|
||||
)
|
||||
from dangerzone import container_utils, errors
|
||||
from dangerzone.isolation_provider.container import Container
|
||||
from dangerzone.isolation_provider.qubes import is_qubes_native_conversion
|
||||
|
||||
from .base import IsolationProviderTermination, IsolationProviderTest
|
||||
|
@ -27,31 +23,27 @@ def provider() -> Container:
|
|||
|
||||
|
||||
class TestContainer(IsolationProviderTest):
|
||||
def test_is_runtime_available_raises(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
def test_is_available_raises(self, provider: Container, fp: FakeProcess) -> None:
|
||||
"""
|
||||
NotAvailableContainerTechException should be raised when
|
||||
the "podman image ls" command fails.
|
||||
"""
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
[container_utils.get_runtime(), "image", "ls"],
|
||||
returncode=-1,
|
||||
stderr="podman image ls logs",
|
||||
)
|
||||
with pytest.raises(NotAvailableContainerTechException):
|
||||
provider.is_runtime_available()
|
||||
with pytest.raises(errors.NotAvailableContainerTechException):
|
||||
provider.is_available()
|
||||
|
||||
def test_is_runtime_available_works(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
def test_is_available_works(self, provider: Container, fp: FakeProcess) -> None:
|
||||
"""
|
||||
No exception should be raised when the "podman image ls" can return properly.
|
||||
"""
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
[container_utils.get_runtime(), "image", "ls"],
|
||||
)
|
||||
provider.is_runtime_available()
|
||||
provider.is_available()
|
||||
|
||||
def test_install_raise_if_image_cant_be_installed(
|
||||
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
|
||||
|
@ -59,17 +51,17 @@ class TestContainer(IsolationProviderTest):
|
|||
"""When an image installation fails, an exception should be raised"""
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
[container_utils.get_runtime(), "image", "ls"],
|
||||
)
|
||||
|
||||
# First check should return nothing.
|
||||
fp.register_subprocess(
|
||||
[
|
||||
provider.get_runtime(),
|
||||
container_utils.get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
"{{ .Tag }}",
|
||||
"dangerzone.rocks/dangerzone",
|
||||
],
|
||||
occurrences=2,
|
||||
|
@ -79,11 +71,11 @@ class TestContainer(IsolationProviderTest):
|
|||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "load"],
|
||||
[container_utils.get_runtime(), "load"],
|
||||
returncode=-1,
|
||||
)
|
||||
|
||||
with pytest.raises(ImageInstallationException):
|
||||
with pytest.raises(errors.ImageInstallationException):
|
||||
provider.install()
|
||||
|
||||
def test_install_raises_if_still_not_installed(
|
||||
|
@ -92,17 +84,17 @@ class TestContainer(IsolationProviderTest):
|
|||
"""When an image keep being not installed, it should return False"""
|
||||
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "image", "ls"],
|
||||
[container_utils.get_runtime(), "image", "ls"],
|
||||
)
|
||||
|
||||
# First check should return nothing.
|
||||
fp.register_subprocess(
|
||||
[
|
||||
provider.get_runtime(),
|
||||
container_utils.get_runtime(),
|
||||
"image",
|
||||
"list",
|
||||
"--format",
|
||||
"{{.ID}}",
|
||||
"{{ .Tag }}",
|
||||
"dangerzone.rocks/dangerzone",
|
||||
],
|
||||
occurrences=2,
|
||||
|
@ -111,9 +103,9 @@ class TestContainer(IsolationProviderTest):
|
|||
# Patch gzip.open and podman load so that it works
|
||||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
fp.register_subprocess(
|
||||
[provider.get_runtime(), "load"],
|
||||
[container_utils.get_runtime(), "load"],
|
||||
)
|
||||
with pytest.raises(ImageNotPresentException):
|
||||
with pytest.raises(errors.ImageNotPresentException):
|
||||
provider.install()
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue