mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-05 21:21:49 +02:00
Compare commits
1 commit
dfcb74b427
...
294c3aacf0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
294c3aacf0 |
66 changed files with 840 additions and 1837 deletions
4
.github/ISSUE_TEMPLATE/bug_report_linux.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report_linux.yml
vendored
|
@ -6,7 +6,7 @@ body:
|
|||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Hi, and thanks for taking the time to open this bug report.
|
||||
Hi, and thanks for taking the time to open this bug report.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
|
@ -21,7 +21,7 @@ body:
|
|||
label: Linux distribution
|
||||
description: |
|
||||
What is the name and version of your Linux distribution? You can find it out with `cat /etc/os-release`
|
||||
placeholder: Ubuntu 22.04.5 LTS
|
||||
placeholder: Ubuntu 20.04.6 LTS
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
|
248
.github/workflows/build-push-image.yml
vendored
248
.github/workflows/build-push-image.yml
vendored
|
@ -1,248 +0,0 @@
|
|||
name: Build and push multi-arch container image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
registry:
|
||||
required: true
|
||||
type: string
|
||||
registry_user:
|
||||
required: true
|
||||
type: string
|
||||
image_name:
|
||||
required: true
|
||||
type: string
|
||||
reproduce:
|
||||
required: true
|
||||
type: boolean
|
||||
secrets:
|
||||
registry_token:
|
||||
required: true
|
||||
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dev. dependencies
|
||||
run: |-
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git python3-poetry --no-install-recommends
|
||||
poetry install --only package
|
||||
|
||||
- name: Verify that the Dockerfile matches the commited template and params
|
||||
run: |-
|
||||
cp Dockerfile Dockerfile.orig
|
||||
make Dockerfile
|
||||
diff Dockerfile.orig Dockerfile
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
debian_archive_date: ${{ steps.params.outputs.debian_archive_date }}
|
||||
source_date_epoch: ${{ steps.params.outputs.source_date_epoch }}
|
||||
image: ${{ steps.params.outputs.full_image_name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compute image parameters
|
||||
id: params
|
||||
run: |
|
||||
source Dockerfile.env
|
||||
DEBIAN_ARCHIVE_DATE=$(date -u +'%Y%m%d')
|
||||
SOURCE_DATE_EPOCH=$(date -u -d ${DEBIAN_ARCHIVE_DATE} +"%s")
|
||||
TAG=${DEBIAN_ARCHIVE_DATE}-$(git describe --long --first-parent | tail -c +2)
|
||||
FULL_IMAGE_NAME=${{ inputs.registry }}/${{ inputs.image_name }}:${TAG}
|
||||
|
||||
echo "debian_archive_date=${DEBIAN_ARCHIVE_DATE}" >> $GITHUB_OUTPUT
|
||||
echo "source_date_epoch=${SOURCE_DATE_EPOCH}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${DEBIAN_ARCHIVE_DATE}-${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "full_image_name=${FULL_IMAGE_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "buildkit_image=${BUILDKIT_IMAGE}" >> $GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform.name }} image
|
||||
runs-on: ${{ matrix.platform.runs-on }}
|
||||
needs:
|
||||
- prepare
|
||||
outputs:
|
||||
debian_archive_date: ${{ needs.prepare.outputs.debian_archive_date }}
|
||||
source_date_epoch: ${{ needs.prepare.outputs.source_date_epoch }}
|
||||
image: ${{ needs.prepare.outputs.image }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- runs-on: "ubuntu-24.04"
|
||||
name: "linux/amd64"
|
||||
- runs-on: "ubuntu-24.04-arm"
|
||||
name: "linux/arm64"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform.name }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ inputs.registry_user }}
|
||||
password: ${{ secrets.registry_token }}
|
||||
|
||||
# Instructions for reproducibly building a container image are taken from:
|
||||
# https://github.com/freedomofpress/repro-build?tab=readme-ov-file#build-and-push-a-container-image-on-github-actions
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: image=${{ needs.prepare.outputs.buildkit_image }}
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./dangerzone/
|
||||
file: Dockerfile
|
||||
build-args: |
|
||||
DEBIAN_ARCHIVE_DATE=${{ needs.prepare.outputs.debian_archive_date }}
|
||||
SOURCE_DATE_EPOCH=${{ needs.prepare.outputs.source_date_epoch }}
|
||||
provenance: false
|
||||
outputs: type=image,"name=${{ inputs.registry }}/${{ inputs.image_name }}",push-by-digest=true,push=true,rewrite-timestamp=true,name-canonical=true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
echo "Image digest is: ${digest}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
outputs:
|
||||
debian_archive_date: ${{ needs.build.outputs.debian_archive_date }}
|
||||
source_date_epoch: ${{ needs.build.outputs.source_date_epoch }}
|
||||
image: ${{ needs.build.outputs.image }}
|
||||
digest_root: ${{ steps.image.outputs.digest_root }}
|
||||
digest_amd64: ${{ steps.image.outputs.digest_amd64 }}
|
||||
digest_arm64: ${{ steps.image.outputs.digest_arm64 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ inputs.registry_user }}
|
||||
password: ${{ secrets.registry_token }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: ${{ runner.temp }}/digests
|
||||
run: |
|
||||
DIGESTS=$(printf '${{ needs.build.outputs.image }}@sha256:%s ' *)
|
||||
docker buildx imagetools create -t ${{ needs.build.outputs.image }} ${DIGESTS}
|
||||
|
||||
- name: Inspect image
|
||||
id: image
|
||||
run: |
|
||||
# Inspect the image
|
||||
docker buildx imagetools inspect ${{ needs.build.outputs.image }}
|
||||
docker buildx imagetools inspect ${{ needs.build.outputs.image }} --format "{{json .Manifest}}" > manifest
|
||||
|
||||
# Calculate and print the digests
|
||||
digest_root=$(jq -r .digest manifest)
|
||||
digest_amd64=$(jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest' manifest)
|
||||
digest_arm64=$(jq -r '.manifests[] | select(.platform.architecture=="arm64") | .digest' manifest)
|
||||
|
||||
echo "The image digests are:"
|
||||
echo " Root: $digest_root"
|
||||
echo " linux/amd64: $digest_amd64"
|
||||
echo " linux/arm64: $digest_arm64"
|
||||
|
||||
# NOTE: Set the digests as an output because the `env` context is not
|
||||
# available to the inputs of a reusable workflow call.
|
||||
echo "digest_root=$digest_root" >> "$GITHUB_OUTPUT"
|
||||
echo "digest_amd64=$digest_amd64" >> "$GITHUB_OUTPUT"
|
||||
echo "digest_arm64=$digest_arm64" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# This step calls the container workflow to generate provenance and push it to
|
||||
# the container registry.
|
||||
provenance:
|
||||
needs:
|
||||
- merge
|
||||
strategy:
|
||||
matrix:
|
||||
manifest_type:
|
||||
- root
|
||||
- amd64
|
||||
- arm64
|
||||
permissions:
|
||||
actions: read # for detecting the Github Actions environment.
|
||||
id-token: write # for creating OIDC tokens for signing.
|
||||
packages: write # for uploading attestations.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
digest: ${{ needs.merge.outputs[format('digest_{0}', matrix.manifest_type)] }}
|
||||
image: ${{ needs.merge.outputs.image }}
|
||||
registry-username: ${{ inputs.registry_user }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.registry_token }}
|
||||
|
||||
# This step ensures that the image is reproducible
|
||||
check-reproducibility:
|
||||
if: ${{ inputs.reproduce }}
|
||||
needs:
|
||||
- merge
|
||||
runs-on: ${{ matrix.platform.runs-on }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- runs-on: "ubuntu-24.04"
|
||||
name: "amd64"
|
||||
- runs-on: "ubuntu-24.04-arm"
|
||||
name: "arm64"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Reproduce the same container image
|
||||
run: |
|
||||
./dev_scripts/reproduce-image.py \
|
||||
--runtime \
|
||||
docker \
|
||||
--debian-archive-date \
|
||||
${{ needs.merge.outputs.debian_archive_date }} \
|
||||
--platform \
|
||||
linux/${{ matrix.platform.name }} \
|
||||
${{ needs.merge.outputs[format('digest_{0}', matrix.platform.name)] }}
|
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
|
@ -33,14 +33,14 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: ubuntu
|
||||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
version: "24.10"
|
||||
- distro: ubuntu
|
||||
version: "25.04"
|
||||
- distro: debian
|
||||
version: bullseye
|
||||
- distro: debian
|
||||
|
@ -51,8 +51,6 @@ jobs:
|
|||
version: "40"
|
||||
- distro: fedora
|
||||
version: "41"
|
||||
- distro: fedora
|
||||
version: "42"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -87,12 +85,19 @@ jobs:
|
|||
id: cache-container-image
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
path: |
|
||||
share/container.tar
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
||||
- name: Build Dangerzone image
|
||||
- name: Build and push Dangerzone image
|
||||
if: ${{ steps.cache-container-image.outputs.cache-hit != 'true' }}
|
||||
run: |
|
||||
sudo apt-get install -y python3-poetry
|
||||
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:$tag \
|
||||
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone:tag
|
||||
|
|
17
.github/workflows/check_pr.yml
vendored
17
.github/workflows/check_pr.yml
vendored
|
@ -1,7 +1,6 @@
|
|||
name: Check branch conformity
|
||||
on:
|
||||
pull_request:
|
||||
types: ["opened", "labeled", "unlabeled", "reopened", "synchronize"]
|
||||
|
||||
jobs:
|
||||
prevent-fixup-commits:
|
||||
|
@ -21,10 +20,16 @@ jobs:
|
|||
|
||||
check-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
name: Ensure CHANGELOG.md is populated for user-visible changes
|
||||
steps:
|
||||
# Pin the GitHub action to a specific commit that we have audited and know
|
||||
# how it works.
|
||||
- uses: tarides/changelog-check-action@509965da3b8ac786a5e2da30c2ccf9661189121f
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
changelog: CHANGELOG.md
|
||||
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
|
14
.github/workflows/check_repos.yml
vendored
14
.github/workflows/check_repos.yml
vendored
|
@ -25,6 +25,8 @@ jobs:
|
|||
version: "24.04" # noble
|
||||
- distro: ubuntu
|
||||
version: "22.04" # jammy
|
||||
- distro: ubuntu
|
||||
version: "20.04" # focal
|
||||
- distro: debian
|
||||
version: "trixie" # 13
|
||||
- distro: debian
|
||||
|
@ -32,6 +34,18 @@ jobs:
|
|||
- distro: debian
|
||||
version: "11" # bullseye
|
||||
steps:
|
||||
- name: Add Podman repo for Ubuntu Focal
|
||||
if: matrix.distro == 'ubuntu' && matrix.version == 20.04
|
||||
run: |
|
||||
apt-get update && apt-get -y install curl wget gnupg2
|
||||
. /etc/os-release
|
||||
sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' \
|
||||
> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- \
|
||||
| apt-key add -
|
||||
apt update
|
||||
apt-get install python-all -y
|
||||
|
||||
- name: Add packages.freedom.press PGP key (gpg)
|
||||
if: matrix.version != 'trixie'
|
||||
run: |
|
||||
|
|
69
.github/workflows/ci.yml
vendored
69
.github/workflows/ci.yml
vendored
|
@ -59,9 +59,9 @@ jobs:
|
|||
id: cache-container-image
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
path: |-
|
||||
share/container.tar
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
|
||||
- name: Build Dangerzone container image
|
||||
|
@ -72,8 +72,8 @@ jobs:
|
|||
- name: Upload container image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: container.tar
|
||||
path: share/container.tar
|
||||
name: container.tar.gz
|
||||
path: share/container.tar.gz
|
||||
|
||||
download-tessdata:
|
||||
name: Download and cache Tesseract data
|
||||
|
@ -186,14 +186,14 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: ubuntu
|
||||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
version: "24.10"
|
||||
- distro: ubuntu
|
||||
version: "25.04"
|
||||
- distro: debian
|
||||
version: bullseye
|
||||
- distro: debian
|
||||
|
@ -226,9 +226,9 @@ jobs:
|
|||
- name: Restore container cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
path: |-
|
||||
share/container.tar
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
fail-on-cache-miss: true
|
||||
|
||||
|
@ -255,14 +255,14 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: ubuntu
|
||||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
version: "24.10"
|
||||
- distro: ubuntu
|
||||
version: "25.04"
|
||||
- distro: debian
|
||||
version: bullseye
|
||||
- distro: debian
|
||||
|
@ -310,7 +310,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
distro: ["fedora"]
|
||||
version: ["40", "41", "42"]
|
||||
version: ["40", "41"]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
@ -333,9 +333,9 @@ jobs:
|
|||
- name: Restore container image
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
path: |-
|
||||
share/container.tar
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
fail-on-cache-miss: true
|
||||
|
||||
|
@ -383,14 +383,14 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- distro: ubuntu
|
||||
version: "20.04"
|
||||
- distro: ubuntu
|
||||
version: "22.04"
|
||||
- distro: ubuntu
|
||||
version: "24.04"
|
||||
- distro: ubuntu
|
||||
version: "24.10"
|
||||
- distro: ubuntu
|
||||
version: "25.04"
|
||||
- distro: debian
|
||||
version: bullseye
|
||||
- distro: debian
|
||||
|
@ -401,8 +401,6 @@ jobs:
|
|||
version: "40"
|
||||
- distro: fedora
|
||||
version: "41"
|
||||
- distro: fedora
|
||||
version: "42"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -430,9 +428,9 @@ jobs:
|
|||
- name: Restore container image
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
|
||||
path: |-
|
||||
share/container.tar
|
||||
share/container.tar.gz
|
||||
share/image-id.txt
|
||||
fail-on-cache-miss: true
|
||||
|
||||
|
@ -473,11 +471,30 @@ jobs:
|
|||
# file successfully.
|
||||
xvfb-run -s '-ac' ./dev_scripts/env.py --distro ${{ matrix.distro }} --version ${{ matrix.version }} run --dev \
|
||||
bash -c 'cd dangerzone; poetry run make test'
|
||||
|
||||
- name: Upload PDF diffs
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
check-reproducibility:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
name: pdf-diffs-${{ matrix.distro }}-${{ matrix.version }}
|
||||
path: tests/test_docs/diffs/*.jpeg
|
||||
# Always run this step to publish test results, even on failures
|
||||
if: ${{ always() }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dev. dependencies
|
||||
run: |-
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git python3-poetry --no-install-recommends
|
||||
poetry install --only package
|
||||
|
||||
- name: Verify that the Dockerfile matches the commited template and params
|
||||
run: |-
|
||||
cp Dockerfile Dockerfile.orig
|
||||
make Dockerfile
|
||||
diff Dockerfile.orig Dockerfile
|
||||
|
||||
- name: Build Dangerzone container image
|
||||
run: |
|
||||
python3 ./install/common/build-image.py --no-save
|
||||
|
||||
- name: Reproduce the same container image
|
||||
run: |
|
||||
./dev_scripts/reproduce-image.py
|
||||
|
|
22
.github/workflows/release-container-image.yml
vendored
22
.github/workflows/release-container-image.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
name: Release multi-arch container image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "test/**"
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Run every day at 00:00 UTC.
|
||||
|
||||
|
||||
jobs:
|
||||
build-push-image:
|
||||
uses: ./.github/workflows/build-push-image.yml
|
||||
with:
|
||||
registry: ghcr.io/${{ github.repository_owner }}
|
||||
registry_user: ${{ github.actor }}
|
||||
image_name: dangerzone/dangerzone
|
||||
reproduce: true
|
||||
secrets:
|
||||
registry_token: ${{ secrets.GITHUB_TOKEN }}
|
17
.github/workflows/scan.yml
vendored
17
.github/workflows/scan.yml
vendored
|
@ -21,12 +21,19 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build container image
|
||||
- name: Install container build dependencies
|
||||
run: |
|
||||
python3 ./install/common/build-image.py \
|
||||
--debian-archive-date $(date "+%Y%m%d") \
|
||||
--runtime docker
|
||||
docker load -i share/container.tar
|
||||
sudo apt install pipx
|
||||
pipx install poetry
|
||||
pipx inject poetry poetry-plugin-export
|
||||
poetry install --only package
|
||||
- name: Bump date of Debian snapshot archive
|
||||
run: |
|
||||
date=$(date "+%Y%m%d")
|
||||
sed -i "s/DEBIAN_ARCHIVE_DATE=[0-9]\+/DEBIAN_ARCHIVE_DATE=${date}/" Dockerfile.env
|
||||
make Dockerfile
|
||||
- name: Build container image
|
||||
run: python3 ./install/common/build-image.py --runtime docker --no-save
|
||||
- name: Get image tag
|
||||
id: tag
|
||||
run: echo "tag=$(cat share/image-id.txt)" >> $GITHUB_OUTPUT
|
||||
|
|
45
BUILD.md
45
BUILD.md
|
@ -34,6 +34,29 @@ Install dependencies:
|
|||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:memo: Expand this section if you are on Ubuntu 20.04 (Focal).</i></summary>
|
||||
</br>
|
||||
|
||||
The default Python version that ships with Ubuntu Focal (3.8) is not
|
||||
compatible with PySide6, which requires Python 3.9 or greater.
|
||||
|
||||
You can install Python 3.9 using the `python3.9` package.
|
||||
|
||||
```bash
|
||||
sudo apt install -y python3.9
|
||||
```
|
||||
|
||||
Poetry will automatically pick up the correct version when running.
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
```sh
|
||||
sudo apt install -y podman dh-python build-essential make libqt6gui6 \
|
||||
pipx python3 python3-dev
|
||||
|
@ -109,6 +132,28 @@ sudo dnf install -y rpm-build podman python3 python3-devel python3-poetry-core \
|
|||
pipx qt6-qtbase-gui
|
||||
```
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:memo: Expand this section if you are on Fedora 41.</i></summary>
|
||||
</br>
|
||||
|
||||
The default Python version that ships with Fedora 41 (3.13) is not
|
||||
compatible with PySide6, which requires Python 3.12 or earlier.
|
||||
|
||||
You can install Python 3.12 using the `python3.12` package.
|
||||
|
||||
```bash
|
||||
sudo dnf install -y python3.12
|
||||
```
|
||||
|
||||
Poetry will automatically pick up the correct version when running.
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Install Poetry using `pipx`:
|
||||
|
||||
```sh
|
||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -7,14 +7,7 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
|
|||
|
||||
## [Unreleased](https://github.com/freedomofpress/dangerzone/compare/v0.8.1...HEAD)
|
||||
|
||||
- Platform support: Drop support for Ubuntu Focal, since it's nearing end-of-life ([#1018](https://github.com/freedomofpress/dangerzone/issues/1018))
|
||||
- Platform support: Add support for Fedora 42 ([#1091](https://github.com/freedomofpress/dangerzone/issues/1091))
|
||||
- Platform support: Add support for Ubuntu 25.04 (Plucky Puffin)([#1090](https://github.com/freedomofpress/dangerzone/issues/1090))
|
||||
|
||||
### Added
|
||||
|
||||
- Document Operating System support [#986](https://github.com/freedomofpress/dangerzone/issues/986)
|
||||
- Tests: Look for regressions when converting PDFs [#321](https://github.com/freedomofpress/dangerzone/issues/321)
|
||||
-
|
||||
|
||||
## [0.8.1](https://github.com/freedomofpress/dangerzone/compare/v0.8.1...0.8.0)
|
||||
|
||||
|
@ -29,10 +22,6 @@ 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))
|
||||
|
||||
## Updated
|
||||
|
||||
- Bump `slsa-framework/slsa-github-generator` from 2.0.0 to 2.1.0 ([#1109](https://github.com/freedomofpress/dangerzone/pull/1109))
|
||||
|
||||
### Development changes
|
||||
|
||||
Thanks [@jkarasti](https://github.com/jkarasti) for the contribution.
|
||||
|
|
79
Dockerfile
79
Dockerfile
|
@ -2,14 +2,14 @@
|
|||
# Dockerfile args below. For more info about this file, read
|
||||
# docs/developer/reproducibility.md.
|
||||
|
||||
ARG DEBIAN_IMAGE_DATE=20250224
|
||||
ARG DEBIAN_IMAGE_DATE=20250113
|
||||
|
||||
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim AS dangerzone-image
|
||||
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim as dangerzone-image
|
||||
|
||||
ARG GVISOR_ARCHIVE_DATE=20250217
|
||||
ARG DEBIAN_ARCHIVE_DATE=20250226
|
||||
ARG H2ORESTART_CHECKSUM=452331f8603ef456264bd72db6fa8a11ca72b392019a8135c0b2f3095037d7b1
|
||||
ARG H2ORESTART_VERSION=v0.7.1
|
||||
ARG GVISOR_ARCHIVE_DATE=20250120
|
||||
ARG DEBIAN_ARCHIVE_DATE=20250127
|
||||
ARG H2ORESTART_CHECKSUM=7760dc2963332c50d15eee285933ec4b48d6a1de9e0c0f6082946f93090bd132
|
||||
ARG H2ORESTART_VERSION=v0.7.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
|
@ -22,8 +22,8 @@ RUN \
|
|||
--mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \
|
||||
--mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \
|
||||
: "Hacky way to set a date for the Debian snapshot repos" && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list.d/debian.sources && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list && \
|
||||
repro-sources-list.sh && \
|
||||
: "Setup APT to install gVisor from its separate APT repo" && \
|
||||
apt-get update && \
|
||||
|
@ -52,13 +52,9 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \
|
|||
&& rm /root/.wget-hsts
|
||||
|
||||
# Create an unprivileged user both for gVisor and for running Dangerzone.
|
||||
# XXX: Make the shadow field "date of last password change" a constant
|
||||
# number.
|
||||
RUN addgroup --gid 1000 dangerzone
|
||||
RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \
|
||||
--disabled-password --home /home/dangerzone dangerzone \
|
||||
&& chage -d 99999 dangerzone \
|
||||
&& rm /etc/shadow-
|
||||
--disabled-password --home /home/dangerzone dangerzone
|
||||
|
||||
# Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to
|
||||
# import it.
|
||||
|
@ -169,50 +165,20 @@ RUN mkdir /home/dangerzone/.containers
|
|||
# The `ln` binary, even if you specify it by its full path, cannot run
|
||||
# (probably because `ld-linux.so` can't be found). For this reason, we have
|
||||
# to create the symlinks beforehand, in a previous build stage. Then, in an
|
||||
# empty container image (scratch images), we can copy these symlinks and the
|
||||
# /usr, and stitch everything together.
|
||||
# empty contianer image (scratch images), we can copy these symlinks and the
|
||||
# /usr, and stich everything together.
|
||||
###############################################################################
|
||||
|
||||
# Create the filesystem hierarchy that will be used to symlink /usr.
|
||||
|
||||
RUN mkdir -p \
|
||||
/new_root \
|
||||
/new_root/root \
|
||||
/new_root/run \
|
||||
/new_root/tmp \
|
||||
/new_root/home/dangerzone/dangerzone-image/rootfs
|
||||
|
||||
# Copy the /etc and /var directories under the new root directory. Also,
|
||||
# copy /etc/, /opt, and /usr to the Dangerzone image rootfs.
|
||||
#
|
||||
# NOTE: We also have to remove the resolv.conf file, in order to not leak any DNS
|
||||
# servers added there during image build time.
|
||||
RUN cp -r /etc /var /new_root/ \
|
||||
&& rm /new_root/etc/resolv.conf
|
||||
RUN cp -r /etc /opt /usr /new_root/home/dangerzone/dangerzone-image/rootfs \
|
||||
&& rm /new_root/home/dangerzone/dangerzone-image/rootfs/etc/resolv.conf
|
||||
|
||||
RUN mkdir /new_root
|
||||
RUN mkdir /new_root/root /new_root/run /new_root/tmp
|
||||
RUN chmod 777 /new_root/tmp
|
||||
RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr
|
||||
RUN ln -s usr/bin /new_root/bin
|
||||
RUN ln -s usr/lib /new_root/lib
|
||||
RUN ln -s usr/lib64 /new_root/lib64
|
||||
RUN ln -s usr/sbin /new_root/sbin
|
||||
RUN ln -s usr/bin /new_root/home/dangerzone/dangerzone-image/rootfs/bin
|
||||
RUN ln -s usr/lib /new_root/home/dangerzone/dangerzone-image/rootfs/lib
|
||||
RUN ln -s usr/lib64 /new_root/home/dangerzone/dangerzone-image/rootfs/lib64
|
||||
|
||||
# Fix permissions in /home/dangerzone, so that our entrypoint script can make
|
||||
# changes in the following folders.
|
||||
RUN chown dangerzone:dangerzone \
|
||||
/new_root/home/dangerzone \
|
||||
/new_root/home/dangerzone/dangerzone-image/
|
||||
# Fix permissions in /tmp, so that it can be used by unprivileged users.
|
||||
RUN chmod 777 /new_root/tmp
|
||||
|
||||
COPY container_helpers/entrypoint.py /new_root
|
||||
# HACK: For reasons that we are not sure yet, we need to explicitly specify the
|
||||
# modification time of this file.
|
||||
RUN touch -d ${DEBIAN_ARCHIVE_DATE}Z /new_root/entrypoint.py
|
||||
|
||||
## Final image
|
||||
|
||||
|
@ -222,7 +188,24 @@ FROM scratch
|
|||
# /usr can be a symlink.
|
||||
COPY --from=dangerzone-image /new_root/ /
|
||||
|
||||
# Copy the bare minimum to run Dangerzone in the inner container image.
|
||||
COPY --from=dangerzone-image /etc/ /home/dangerzone/dangerzone-image/rootfs/etc/
|
||||
COPY --from=dangerzone-image /opt/ /home/dangerzone/dangerzone-image/rootfs/opt/
|
||||
COPY --from=dangerzone-image /usr/ /home/dangerzone/dangerzone-image/rootfs/usr/
|
||||
RUN ln -s usr/bin /home/dangerzone/dangerzone-image/rootfs/bin
|
||||
RUN ln -s usr/lib /home/dangerzone/dangerzone-image/rootfs/lib
|
||||
RUN ln -s usr/lib64 /home/dangerzone/dangerzone-image/rootfs/lib64
|
||||
|
||||
# Copy the bare minimum to let the security scanner find vulnerabilities.
|
||||
COPY --from=dangerzone-image /etc/ /etc/
|
||||
COPY --from=dangerzone-image /var/ /var/
|
||||
|
||||
# Allow our entrypoint script to make changes in the following folders.
|
||||
RUN chown dangerzone:dangerzone /home/dangerzone /home/dangerzone/dangerzone-image/
|
||||
|
||||
# Switch to the dangerzone user for the rest of the script.
|
||||
USER dangerzone
|
||||
|
||||
COPY container_helpers/entrypoint.py /
|
||||
|
||||
ENTRYPOINT ["/entrypoint.py"]
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
# Can be bumped to the latest date in https://hub.docker.com/_/debian/tags?name=bookworm-
|
||||
DEBIAN_IMAGE_DATE=20250224
|
||||
DEBIAN_IMAGE_DATE=20250113
|
||||
# Can be bumped to today's date
|
||||
DEBIAN_ARCHIVE_DATE=20250226
|
||||
DEBIAN_ARCHIVE_DATE=20250127
|
||||
# Can be bumped to the latest date in https://github.com/google/gvisor/tags
|
||||
GVISOR_ARCHIVE_DATE=20250217
|
||||
GVISOR_ARCHIVE_DATE=20250120
|
||||
# Can be bumped to the latest version and checksum from https://github.com/ebandal/H2Orestart/releases
|
||||
H2ORESTART_CHECKSUM=452331f8603ef456264bd72db6fa8a11ca72b392019a8135c0b2f3095037d7b1
|
||||
H2ORESTART_VERSION=v0.7.1
|
||||
|
||||
# Buildkit image (taken from freedomofpress/repro-build)
|
||||
BUILDKIT_IMAGE="docker.io/moby/buildkit:v19.0@sha256:14aa1b4dd92ea0a4cd03a54d0c6079046ea98cd0c0ae6176bdd7036ba370cbbe"
|
||||
BUILDKIT_IMAGE_ROOTLESS="docker.io/moby/buildkit:v0.19.0-rootless@sha256:e901cffdad753892a7c3afb8b9972549fca02c73888cf340c91ed801fdd96d71"
|
||||
H2ORESTART_CHECKSUM=7760dc2963332c50d15eee285933ec4b48d6a1de9e0c0f6082946f93090bd132
|
||||
H2ORESTART_VERSION=v0.7.0
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
ARG DEBIAN_IMAGE_DATE={{DEBIAN_IMAGE_DATE}}
|
||||
|
||||
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim AS dangerzone-image
|
||||
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim as dangerzone-image
|
||||
|
||||
ARG GVISOR_ARCHIVE_DATE={{GVISOR_ARCHIVE_DATE}}
|
||||
ARG DEBIAN_ARCHIVE_DATE={{DEBIAN_ARCHIVE_DATE}}
|
||||
|
@ -22,8 +22,8 @@ RUN \
|
|||
--mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \
|
||||
--mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \
|
||||
: "Hacky way to set a date for the Debian snapshot repos" && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list.d/debian.sources && \
|
||||
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list && \
|
||||
repro-sources-list.sh && \
|
||||
: "Setup APT to install gVisor from its separate APT repo" && \
|
||||
apt-get update && \
|
||||
|
@ -52,13 +52,9 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \
|
|||
&& rm /root/.wget-hsts
|
||||
|
||||
# Create an unprivileged user both for gVisor and for running Dangerzone.
|
||||
# XXX: Make the shadow field "date of last password change" a constant
|
||||
# number.
|
||||
RUN addgroup --gid 1000 dangerzone
|
||||
RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \
|
||||
--disabled-password --home /home/dangerzone dangerzone \
|
||||
&& chage -d 99999 dangerzone \
|
||||
&& rm /etc/shadow-
|
||||
--disabled-password --home /home/dangerzone dangerzone
|
||||
|
||||
# Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to
|
||||
# import it.
|
||||
|
@ -169,50 +165,20 @@ RUN mkdir /home/dangerzone/.containers
|
|||
# The `ln` binary, even if you specify it by its full path, cannot run
|
||||
# (probably because `ld-linux.so` can't be found). For this reason, we have
|
||||
# to create the symlinks beforehand, in a previous build stage. Then, in an
|
||||
# empty container image (scratch images), we can copy these symlinks and the
|
||||
# /usr, and stitch everything together.
|
||||
# empty contianer image (scratch images), we can copy these symlinks and the
|
||||
# /usr, and stich everything together.
|
||||
###############################################################################
|
||||
|
||||
# Create the filesystem hierarchy that will be used to symlink /usr.
|
||||
|
||||
RUN mkdir -p \
|
||||
/new_root \
|
||||
/new_root/root \
|
||||
/new_root/run \
|
||||
/new_root/tmp \
|
||||
/new_root/home/dangerzone/dangerzone-image/rootfs
|
||||
|
||||
# Copy the /etc and /var directories under the new root directory. Also,
|
||||
# copy /etc/, /opt, and /usr to the Dangerzone image rootfs.
|
||||
#
|
||||
# NOTE: We also have to remove the resolv.conf file, in order to not leak any
|
||||
# DNS servers added there during image build time.
|
||||
RUN cp -r /etc /var /new_root/ \
|
||||
&& rm /new_root/etc/resolv.conf
|
||||
RUN cp -r /etc /opt /usr /new_root/home/dangerzone/dangerzone-image/rootfs \
|
||||
&& rm /new_root/home/dangerzone/dangerzone-image/rootfs/etc/resolv.conf
|
||||
|
||||
RUN mkdir /new_root
|
||||
RUN mkdir /new_root/root /new_root/run /new_root/tmp
|
||||
RUN chmod 777 /new_root/tmp
|
||||
RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr
|
||||
RUN ln -s usr/bin /new_root/bin
|
||||
RUN ln -s usr/lib /new_root/lib
|
||||
RUN ln -s usr/lib64 /new_root/lib64
|
||||
RUN ln -s usr/sbin /new_root/sbin
|
||||
RUN ln -s usr/bin /new_root/home/dangerzone/dangerzone-image/rootfs/bin
|
||||
RUN ln -s usr/lib /new_root/home/dangerzone/dangerzone-image/rootfs/lib
|
||||
RUN ln -s usr/lib64 /new_root/home/dangerzone/dangerzone-image/rootfs/lib64
|
||||
|
||||
# Fix permissions in /home/dangerzone, so that our entrypoint script can make
|
||||
# changes in the following folders.
|
||||
RUN chown dangerzone:dangerzone \
|
||||
/new_root/home/dangerzone \
|
||||
/new_root/home/dangerzone/dangerzone-image/
|
||||
# Fix permissions in /tmp, so that it can be used by unprivileged users.
|
||||
RUN chmod 777 /new_root/tmp
|
||||
|
||||
COPY container_helpers/entrypoint.py /new_root
|
||||
# HACK: For reasons that we are not sure yet, we need to explicitly specify the
|
||||
# modification time of this file.
|
||||
RUN touch -d ${DEBIAN_ARCHIVE_DATE}Z /new_root/entrypoint.py
|
||||
|
||||
## Final image
|
||||
|
||||
|
@ -222,7 +188,24 @@ FROM scratch
|
|||
# /usr can be a symlink.
|
||||
COPY --from=dangerzone-image /new_root/ /
|
||||
|
||||
# Copy the bare minimum to run Dangerzone in the inner container image.
|
||||
COPY --from=dangerzone-image /etc/ /home/dangerzone/dangerzone-image/rootfs/etc/
|
||||
COPY --from=dangerzone-image /opt/ /home/dangerzone/dangerzone-image/rootfs/opt/
|
||||
COPY --from=dangerzone-image /usr/ /home/dangerzone/dangerzone-image/rootfs/usr/
|
||||
RUN ln -s usr/bin /home/dangerzone/dangerzone-image/rootfs/bin
|
||||
RUN ln -s usr/lib /home/dangerzone/dangerzone-image/rootfs/lib
|
||||
RUN ln -s usr/lib64 /home/dangerzone/dangerzone-image/rootfs/lib64
|
||||
|
||||
# Copy the bare minimum to let the security scanner find vulnerabilities.
|
||||
COPY --from=dangerzone-image /etc/ /etc/
|
||||
COPY --from=dangerzone-image /var/ /var/
|
||||
|
||||
# Allow our entrypoint script to make changes in the following folders.
|
||||
RUN chown dangerzone:dangerzone /home/dangerzone /home/dangerzone/dangerzone-image/
|
||||
|
||||
# Switch to the dangerzone user for the rest of the script.
|
||||
USER dangerzone
|
||||
|
||||
COPY container_helpers/entrypoint.py /
|
||||
|
||||
ENTRYPOINT ["/entrypoint.py"]
|
||||
|
|
90
INSTALL.md
90
INSTALL.md
|
@ -1,42 +1,8 @@
|
|||
## Operating System support
|
||||
|
||||
Dangerzone can run on various Operating Systems (OS), and has automated tests
|
||||
for most of them.
|
||||
This section explains which OS we support, how long we support each version, and
|
||||
how do we test Dangerzone against these.
|
||||
|
||||
You can find general support information in this table, and more details in the
|
||||
following sections.
|
||||
|
||||
(Unless specified, the architecture of the OS is AMD64)
|
||||
|
||||
| Distribution | Supported releases | Automated tests | Manual QA |
|
||||
| ------------ | ------------------------- | ---------------------- | --------- |
|
||||
| Windows | 2 last releases | 🗹 (`windows-latest`) ◎ | 🗹 |
|
||||
| macOS intel | 3 last releases | 🗹 (`macos-13`) ◎ | 🗹 |
|
||||
| macOS silicon | 3 last releases | 🗹 (`macos-latest`) ◎ | 🗹 |
|
||||
| Ubuntu | Follow upstream support ✰ | 🗹 | 🗹 |
|
||||
| Debian | Current stable, Oldstable and LTS releases | 🗹 | 🗹 |
|
||||
| Fedora | Follow upstream support | 🗹 | 🗹 |
|
||||
| Qubes OS | [Beta support](https://github.com/freedomofpress/dangerzone/issues/413) ✢ | 🗷 | Latest Fedora template |
|
||||
| Tails | Only the last release | 🗷 | Last release only |
|
||||
|
||||
Notes:
|
||||
|
||||
✰ Support for Ubuntu Focal [was dropped](https://github.com/freedomofpress/dangerzone/issues/1018)
|
||||
|
||||
✢ Qubes OS support assumes the use of a Fedora template. The supported releases follow our general support for Fedora.
|
||||
|
||||
◎ More information about where that points [in the runner-images repository](https://github.com/actions/runner-images/tree/main)
|
||||
|
||||
## MacOS
|
||||
|
||||
- Download [Dangerzone 0.8.1 for Mac (Apple Silicon CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.8.1/Dangerzone-0.8.1-arm64.dmg)
|
||||
- Download [Dangerzone 0.8.1 for Mac (Intel CPU)](https://github.com/freedomofpress/dangerzone/releases/download/v0.8.1/Dangerzone-0.8.1-i686.dmg)
|
||||
|
||||
> [!TIP]
|
||||
> We support the releases of macOS that are still within Apple's servicing timeline. Apple usually provides security updates for the latest 3 releases, but this isn’t consistently applied and security fixes aren’t guaranteed for the non-latest releases. We are also dependent on [Docker Desktop windows support](https://docs.docker.com/desktop/setup/install/mac-install/)
|
||||
|
||||
You can also install Dangerzone for Mac using [Homebrew](https://brew.sh/): `brew install --cask dangerzone`
|
||||
|
||||
> **Note**: you will also need to install [Docker Desktop](https://www.docker.com/products/docker-desktop/).
|
||||
|
@ -51,38 +17,18 @@ You can also install Dangerzone for Mac using [Homebrew](https://brew.sh/): `bre
|
|||
> This program needs to run alongside Dangerzone at all times, since it is what allows Dangerzone to
|
||||
> create the secure environment.
|
||||
|
||||
> [!TIP]
|
||||
> We generally support Windows releases that are still within [Microsoft’s servicing timeline](https://support.microsoft.com/en-us/help/13853/windows-lifecycle-fact-sheet).
|
||||
>
|
||||
> Docker sets the bottom line:
|
||||
>
|
||||
> > Docker only supports Docker Desktop on Windows for those versions of Windows that are still within [Microsoft’s servicing timeline](https://support.microsoft.com/en-us/help/13853/windows-lifecycle-fact-sheet). Docker Desktop is not supported on server versions of Windows, such as Windows Server 2019 or Windows Server 2022.
|
||||
|
||||
|
||||
## Linux
|
||||
|
||||
On Linux, Dangerzone uses [Podman](https://podman.io/) instead of Docker Desktop for creating
|
||||
an isolated environment. It will be installed automatically when installing Dangerzone.
|
||||
|
||||
> [!TIP]
|
||||
> We support Ubuntu, Debian, and Fedora releases that are still within
|
||||
> their respective servicing timelines, with a few twists:
|
||||
>
|
||||
> - Ubuntu: We follow upstream support with an extra cutoff date. No support for
|
||||
> versions prior to the second oldest LTS release.
|
||||
> - Fedora: We follow upstream support
|
||||
> - Debian: current stable, oldstable and LTS releases.
|
||||
|
||||
Dangerzone is available for:
|
||||
|
||||
- Ubuntu 25.04 (plucky)
|
||||
- Ubuntu 24.10 (oracular)
|
||||
- Ubuntu 24.04 (noble)
|
||||
- Ubuntu 22.04 (jammy)
|
||||
- Ubuntu 20.04 (focal)
|
||||
- Debian 13 (trixie)
|
||||
- Debian 12 (bookworm)
|
||||
- Debian 11 (bullseye)
|
||||
- Fedora 42
|
||||
- Fedora 41
|
||||
- Fedora 40
|
||||
- Tails
|
||||
|
@ -94,7 +40,35 @@ Dangerzone is available for:
|
|||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:information_source: Backport notice for Ubuntu 22.04 (Jammy) users regarding the <code>conmon</code> package</i></summary>
|
||||
<summary><i>:memo: Expand this section if you are on Ubuntu 20.04 (Focal).</i></summary>
|
||||
</br>
|
||||
|
||||
Dangerzone requires [Podman](https://podman.io/), which is not available
|
||||
through the official Ubuntu Focal repos. To proceed with the Dangerzone
|
||||
installation, you need to add an extra OpenSUSE repo that provides Podman to
|
||||
Ubuntu Focal users. You can follow the instructions below, which have been
|
||||
copied from the [official Podman blog](https://podman.io/new/2021/06/16/new.html):
|
||||
|
||||
```bash
|
||||
sudo apt-get update && sudo apt-get install curl wget gnupg2 -y
|
||||
. /etc/os-release
|
||||
sudo sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' \
|
||||
> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- \
|
||||
| sudo apt-key add -
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:information_source: Backport notice for Ubuntu 24.04 (Noble) users regarding the <code>conmon</code> package</i></summary>
|
||||
</br>
|
||||
|
||||
The `conmon` version that Podman uses and Ubuntu Jammy ships, has a bug
|
||||
|
@ -323,7 +297,7 @@ Our [GitHub Releases page](https://github.com/freedomofpress/dangerzone/releases
|
|||
hosts the following files:
|
||||
* Windows installer (`Dangerzone-<version>.msi`)
|
||||
* macOS archives (`Dangerzone-<version>-<arch>.dmg`)
|
||||
* Container images (`container-<version>-<arch>.tar`)
|
||||
* Container images (`container-<version>-<arch>.tar.gz`)
|
||||
* Source package (`dangerzone-<version>.tar.gz`)
|
||||
|
||||
All these files are accompanied by signatures (as `.asc` files). We'll explain
|
||||
|
@ -351,7 +325,7 @@ gpg --verify Dangerzone-0.6.1-i686.dmg.asc Dangerzone-0.6.1-i686.dmg
|
|||
For the container images:
|
||||
|
||||
```
|
||||
gpg --verify container-0.6.1-i686.tar.asc container-0.6.1-i686.tar
|
||||
gpg --verify container-0.6.1-i686.tar.gz.asc container-0.6.1-i686.tar.gz
|
||||
```
|
||||
|
||||
For the source package:
|
||||
|
|
3
Makefile
3
Makefile
|
@ -66,9 +66,6 @@ build-macos-arm: build-clean
|
|||
build-linux: build-clean
|
||||
doit -n 8 fedora_rpm debian_deb
|
||||
|
||||
.PHONY: regenerate-reference-pdfs
|
||||
regenerate-reference-pdfs:
|
||||
pytest tests/test_cli.py -k regenerate --generate-reference-pdfs
|
||||
# Makefile self-help borrowed from the securedrop-client project
|
||||
# Explaination of the below shell command should it ever break.
|
||||
# 1. Set the field separator to ": ##" and any make targets that might appear between : and ##
|
||||
|
|
|
@ -22,8 +22,6 @@ Follow the instructions for each platform:
|
|||
* [Qubes OS (beta)](https://github.com/freedomofpress/dangerzone/blob/v0.8.0/INSTALL.md#qubes-os)
|
||||
* [Tails](https://github.com/freedomofpress/dangerzone/blob/v0.8.1/INSTALL.md#tails)
|
||||
|
||||
You can read more about our operating system support [here](https://github.com/freedomofpress/dangerzone/blob/v0.8.1/INSTALL.md#operating-system-support).
|
||||
|
||||
## Some features
|
||||
|
||||
- Sandboxes don't have network access, so if a malicious document can compromise one, it can't phone home
|
||||
|
|
|
@ -151,7 +151,7 @@ Here is what you need to do:
|
|||
poetry run ./install/common/download-tessdata.py
|
||||
|
||||
# Copy the container image to the assets folder
|
||||
cp share/container.tar ~dz/release-assets/$VERSION/dangerzone-$VERSION-arm64.tar
|
||||
cp share/container.tar.gz ~dz/release-assets/$VERSION/dangerzone-$VERSION-arm64.tar.gz
|
||||
cp share/image-id.txt ~dz/release-assets/$VERSION/.
|
||||
```
|
||||
|
||||
|
@ -228,7 +228,7 @@ The Windows release is performed in a Windows 11 virtual machine (as opposed to
|
|||
|
||||
- [ ] 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` and `share/image-id.txt` from the host into the `share` folder in the VM.
|
||||
> 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.
|
||||
- [ ] Run `poetry run .\install\windows\build-app.bat`
|
||||
- [ ] When you're done you will have `dist\Dangerzone.msi`
|
||||
|
||||
|
@ -319,8 +319,9 @@ To publish the release, you can follow these steps:
|
|||
|
||||
- [ ] 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 ./share/container.tar:/container.tar anchore/grype:latest /container.tar
|
||||
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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import gzip
|
||||
import logging
|
||||
import platform
|
||||
import shutil
|
||||
|
@ -95,26 +96,18 @@ def list_image_tags() -> List[str]:
|
|||
)
|
||||
|
||||
|
||||
def add_image_tag(image_id: str, new_tag: str) -> None:
|
||||
"""Add a tag to the Dangerzone image."""
|
||||
log.debug(f"Adding tag '{new_tag}' to image '{image_id}'")
|
||||
subprocess.check_output(
|
||||
[get_runtime(), "tag", image_id, new_tag],
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
|
||||
|
||||
def delete_image_tag(tag: str) -> None:
|
||||
"""Delete a Dangerzone image tag."""
|
||||
log.warning(f"Deleting old container image: {tag}")
|
||||
name = CONTAINER_NAME + ":" + tag
|
||||
log.warning(f"Deleting old container image: {name}")
|
||||
try:
|
||||
subprocess.check_output(
|
||||
[get_runtime(), "rmi", "--force", tag],
|
||||
[get_runtime(), "rmi", "--force", name],
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(
|
||||
f"Couldn't delete old container image '{tag}', so leaving it there."
|
||||
f"Couldn't delete old container image '{name}', so leaving it there."
|
||||
f" Original error: {e}"
|
||||
)
|
||||
|
||||
|
@ -127,44 +120,30 @@ def get_expected_tag() -> str:
|
|||
|
||||
def load_image_tarball() -> None:
|
||||
log.info("Installing Dangerzone container image...")
|
||||
tarball_path = get_resource_path("container.tar")
|
||||
try:
|
||||
res = subprocess.run(
|
||||
[get_runtime(), "load", "-i", tarball_path],
|
||||
startupinfo=get_subprocess_startupinfo(),
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.stderr:
|
||||
error = e.stderr.decode()
|
||||
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}"
|
||||
)
|
||||
|
||||
# Loading an image built with Buildkit in Podman 3.4 messes up its name. The tag
|
||||
# somehow becomes the name of the loaded image [1].
|
||||
#
|
||||
# We know that older Podman versions are not generally affected, since Podman v3.0.1
|
||||
# on Debian Bullseye works properly. Also, Podman v4.0 is not affected, so it makes
|
||||
# sense to target only Podman v3.4 for a fix.
|
||||
#
|
||||
# The fix is simple, tag the image properly based on the expected tag from
|
||||
# `share/image-id.txt` and delete the incorrect tag.
|
||||
#
|
||||
# [1] https://github.com/containers/podman/issues/16490
|
||||
if get_runtime_name() == "podman" and get_runtime_version() == (3, 4):
|
||||
expected_tag = get_expected_tag()
|
||||
bad_tag = f"localhost/{expected_tag}:latest"
|
||||
good_tag = f"{CONTAINER_NAME}:{expected_tag}"
|
||||
|
||||
log.debug(
|
||||
f"Dangerzone images loaded in Podman v3.4 usually have an invalid tag."
|
||||
" Fixing it..."
|
||||
)
|
||||
add_image_tag(bad_tag, good_tag)
|
||||
delete_image_tag(bad_tag)
|
||||
|
||||
log.info("Successfully installed container image")
|
||||
log.info("Successfully installed container image from")
|
||||
|
|
|
@ -3,7 +3,6 @@ import os
|
|||
import platform
|
||||
import tempfile
|
||||
import typing
|
||||
from multiprocessing import freeze_support
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
@ -56,6 +55,13 @@ about updates.</p>
|
|||
HAMBURGER_MENU_SIZE = 30
|
||||
|
||||
|
||||
WARNING_MESSAGE = """\
|
||||
<p><b>Warning:</b> Ubuntu Focal systems and their derivatives will
|
||||
stop being supported in subsequent Dangerzone releases. We encourage you to upgrade to a
|
||||
more recent version of your operating system in order to get security updates.</p>
|
||||
"""
|
||||
|
||||
|
||||
def load_svg_image(filename: str, width: int, height: int) -> QtGui.QPixmap:
|
||||
"""Load an SVG image from a filename.
|
||||
|
||||
|
@ -186,9 +192,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
header_layout.addWidget(self.hamburger_button)
|
||||
header_layout.addSpacing(15)
|
||||
|
||||
# Content widget, contains all the window content except waiting widget
|
||||
self.content_widget = ContentWidget(self.dangerzone)
|
||||
|
||||
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)
|
||||
|
@ -198,6 +201,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.waiting_widget = WaitingWidget()
|
||||
self.dangerzone.is_waiting_finished = True
|
||||
|
||||
# Content widget, contains all the window content except waiting widget
|
||||
self.content_widget = ContentWidget(self.dangerzone)
|
||||
|
||||
# Only use the waiting widget if container runtime isn't available
|
||||
if self.dangerzone.is_waiting_finished:
|
||||
self.waiting_widget.hide()
|
||||
|
@ -620,6 +626,17 @@ class ContentWidget(QtWidgets.QWidget):
|
|||
self.dangerzone = dangerzone
|
||||
self.conversion_started = False
|
||||
|
||||
self.warning_label = None
|
||||
if platform.system() == "Linux":
|
||||
# Add the warning message only for ubuntu focal
|
||||
os_release_path = Path("/etc/os-release")
|
||||
if os_release_path.exists():
|
||||
os_release = os_release_path.read_text()
|
||||
if "Ubuntu 20.04" in os_release or "focal" in os_release:
|
||||
self.warning_label = QtWidgets.QLabel(WARNING_MESSAGE)
|
||||
self.warning_label.setWordWrap(True)
|
||||
self.warning_label.setProperty("style", "warning")
|
||||
|
||||
# Doc selection widget
|
||||
self.doc_selection_widget = DocSelectionWidget(self.dangerzone)
|
||||
self.doc_selection_widget.documents_selected.connect(self.documents_selected)
|
||||
|
@ -645,6 +662,8 @@ class ContentWidget(QtWidgets.QWidget):
|
|||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
if self.warning_label:
|
||||
layout.addWidget(self.warning_label) # Add warning at the top
|
||||
layout.addWidget(self.settings_widget, stretch=1)
|
||||
layout.addWidget(self.documents_list, stretch=1)
|
||||
layout.addWidget(self.doc_selection_wrapper, stretch=1)
|
||||
|
@ -875,16 +894,22 @@ class SettingsWidget(QtWidgets.QWidget):
|
|||
self.safe_extension_name_layout.setSpacing(0)
|
||||
self.safe_extension_name_layout.addWidget(self.safe_extension_filename)
|
||||
self.safe_extension_name_layout.addWidget(self.safe_extension)
|
||||
self.dot_pdf_validator = QtGui.QRegularExpressionValidator(
|
||||
QtCore.QRegularExpression(r".*\.[Pp][Dd][Ff]")
|
||||
)
|
||||
# FIXME: Workaround for https://github.com/freedomofpress/dangerzone/issues/339.
|
||||
# We should drop this once we drop Ubuntu Focal support.
|
||||
if hasattr(QtGui, "QRegularExpressionValidator"):
|
||||
QRegEx = QtCore.QRegularExpression
|
||||
QRegExValidator = QtGui.QRegularExpressionValidator
|
||||
else:
|
||||
QRegEx = QtCore.QRegExp # type: ignore [assignment]
|
||||
QRegExValidator = QtGui.QRegExpValidator # type: ignore [assignment]
|
||||
self.dot_pdf_validator = QRegExValidator(QRegEx(r".*\.[Pp][Dd][Ff]"))
|
||||
if platform.system() == "Linux":
|
||||
illegal_chars_regex = r"[/]"
|
||||
elif platform.system() == "Darwin":
|
||||
illegal_chars_regex = r"[\\]"
|
||||
else:
|
||||
illegal_chars_regex = r"[\"*/:<>?\\|]"
|
||||
self.illegal_chars_regex = QtCore.QRegularExpression(illegal_chars_regex)
|
||||
self.illegal_chars_regex = QRegEx(illegal_chars_regex)
|
||||
self.safe_extension_layout = QtWidgets.QHBoxLayout()
|
||||
self.safe_extension_layout.addWidget(self.save_checkbox)
|
||||
self.safe_extension_layout.addWidget(self.safe_extension_label)
|
||||
|
@ -1221,9 +1246,6 @@ class DocumentsListWidget(QtWidgets.QListWidget):
|
|||
def start_conversion(self) -> None:
|
||||
if not self.thread_pool_initized:
|
||||
max_jobs = self.dangerzone.isolation_provider.get_max_parallel_conversions()
|
||||
# Call freeze_support() to avoid passing unknown options to the subprocess.
|
||||
# See https://github.com/freedomofpress/dangerzone/issues/873
|
||||
freeze_support()
|
||||
self.thread_pool = ThreadPool(max_jobs)
|
||||
|
||||
for doc in self.docs_list:
|
||||
|
|
|
@ -97,7 +97,6 @@ class Container(IsolationProvider):
|
|||
f"Could not find a Dangerzone container image with tag '{expected_tag}'"
|
||||
)
|
||||
for tag in old_tags:
|
||||
tag = container_utils.CONTAINER_NAME + ":" + tag
|
||||
container_utils.delete_image_tag(tag)
|
||||
else:
|
||||
return True
|
||||
|
|
|
@ -130,7 +130,7 @@ def is_qubes_native_conversion() -> bool:
|
|||
# This disambiguates if it is running a Qubes targetted build or not
|
||||
# (Qubes-specific builds don't ship the container image)
|
||||
|
||||
container_image_path = get_resource_path("container.tar")
|
||||
return not os.path.exists(container_image_path)
|
||||
compressed_container_path = get_resource_path("container.tar.gz")
|
||||
return not os.path.exists(compressed_container_path)
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -58,7 +58,7 @@ def get_tessdata_dir() -> pathlib.Path:
|
|||
pathlib.Path("/usr/share/tessdata/"), # on some Debian
|
||||
pathlib.Path("/usr/share/tesseract/tessdata/"), # on Fedora
|
||||
pathlib.Path("/usr/share/tesseract-ocr/tessdata/"), # ? (documented)
|
||||
pathlib.Path("/usr/share/tesseract-ocr/4.00/tessdata/"), # on Debian Bullseye
|
||||
pathlib.Path("/usr/share/tesseract-ocr/4.00/tessdata/"), # on Ubuntu Focal
|
||||
pathlib.Path("/usr/share/tesseract-ocr/5/tessdata/"), # on Debian Trixie
|
||||
]
|
||||
|
||||
|
|
|
@ -60,6 +60,24 @@ Run Dangerzone in the end-user environment:
|
|||
|
||||
"""
|
||||
|
||||
# NOTE: For Ubuntu 20.04 specifically, we need to install some extra deps, mainly for
|
||||
# Podman. This needs to take place both in our dev and end-user environment. See the
|
||||
# corresponding note in our Installation section:
|
||||
#
|
||||
# https://github.com/freedomofpress/dangerzone/blob/main/INSTALL.md#ubuntu-debian
|
||||
DOCKERFILE_UBUNTU_2004_DEPS = r"""
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y python-all python3.9 curl wget gnupg2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN . /etc/os-release \
|
||||
&& sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$VERSION_ID/ /' \
|
||||
> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list" \
|
||||
&& wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_$VERSION_ID/Release.key -O- \
|
||||
| apt-key add -
|
||||
"""
|
||||
|
||||
# XXX: overcome the fact that ubuntu images (starting on 23.04) ship with the 'ubuntu'
|
||||
# user by default https://bugs.launchpad.net/cloud-images/+bug/2005129
|
||||
# Related issue https://github.com/freedomofpress/dangerzone/pull/461
|
||||
|
@ -96,18 +114,33 @@ RUN apt-get update \
|
|||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends dh-python make build-essential \
|
||||
git {qt_deps} pipx python3 python3-pip python3-venv dpkg-dev debhelper python3-setuptools \
|
||||
python3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pipx install poetry
|
||||
# NOTE: `pipx install poetry` fails on Ubuntu Focal, when installed through APT. By
|
||||
# installing the latest version, we sidestep this issue.
|
||||
RUN bash -c 'if [[ "$(pipx --version)" < "1" ]]; then \
|
||||
apt-get update \
|
||||
&& apt-get remove -y pipx \
|
||||
&& apt-get install -y --no-install-recommends python3-pip \
|
||||
&& pip install pipx \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
else true; fi'
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends mupdf thunar \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
"""
|
||||
|
||||
# NOTE: Fedora 41 comes with Python 3.13 installed. Our Python project is not compatible
|
||||
# yet with Python 3.13, because PySide6 cannot work with this Python version. To
|
||||
# sidestep this, install Python 3.12 *only* in dev environments.
|
||||
DOCKERFILE_BUILD_DEV_FEDORA_41_DEPS = r"""
|
||||
# Install Python 3.12 since our project is not compatible yet with Python 3.13.
|
||||
RUN dnf install -y python3.12
|
||||
"""
|
||||
|
||||
# FIXME: Install Poetry on Fedora via package manager.
|
||||
DOCKERFILE_BUILD_DEV_FEDORA_DEPS = r"""
|
||||
RUN dnf install -y git rpm-build podman python3 python3-devel python3-poetry-core \
|
||||
pipx make qt6-qtbase-gui gcc gcc-c++\
|
||||
pipx make qt6-qtbase-gui \
|
||||
&& dnf clean all
|
||||
|
||||
# FIXME: Drop this fix after it's resolved upstream.
|
||||
|
@ -531,6 +564,8 @@ class Env:
|
|||
|
||||
if self.distro == "fedora":
|
||||
install_deps = DOCKERFILE_BUILD_DEV_FEDORA_DEPS
|
||||
if self.version == "41":
|
||||
install_deps += DOCKERFILE_BUILD_DEV_FEDORA_41_DEPS
|
||||
else:
|
||||
# Use Qt6 in all of our Linux dev environments, and add a missing
|
||||
# libxcb-cursor0 dependency
|
||||
|
@ -538,7 +573,12 @@ class Env:
|
|||
# See https://github.com/freedomofpress/dangerzone/issues/482
|
||||
qt_deps = "libqt6gui6 libxcb-cursor0"
|
||||
install_deps = DOCKERFILE_BUILD_DEV_DEBIAN_DEPS
|
||||
if self.distro == "ubuntu" and self.version in ("22.04", "jammy"):
|
||||
if self.distro == "ubuntu" and self.version in ("20.04", "focal"):
|
||||
qt_deps = "libqt5gui5 libxcb-cursor0" # Ubuntu Focal has only Qt5.
|
||||
install_deps = (
|
||||
DOCKERFILE_UBUNTU_2004_DEPS + DOCKERFILE_BUILD_DEV_DEBIAN_DEPS
|
||||
)
|
||||
elif self.distro == "ubuntu" and self.version in ("22.04", "jammy"):
|
||||
# Ubuntu Jammy misses a dependency to `libxkbcommon-x11-0`, which we can
|
||||
# install indirectly via `qt6-qpa-plugins`.
|
||||
qt_deps += " qt6-qpa-plugins"
|
||||
|
@ -552,8 +592,6 @@ class Env:
|
|||
"noble",
|
||||
"24.10",
|
||||
"ocular",
|
||||
"25.04",
|
||||
"plucky",
|
||||
):
|
||||
install_deps = (
|
||||
DOCKERFILE_UBUNTU_REM_USER + DOCKERFILE_BUILD_DEV_DEBIAN_DEPS
|
||||
|
@ -604,7 +642,11 @@ class Env:
|
|||
install_cmd = "dnf install -y"
|
||||
else:
|
||||
install_deps = DOCKERFILE_BUILD_DEBIAN_DEPS
|
||||
if self.distro == "ubuntu" and self.version in ("22.04", "jammy"):
|
||||
if self.distro == "ubuntu" and self.version in ("20.04", "focal"):
|
||||
install_deps = (
|
||||
DOCKERFILE_UBUNTU_2004_DEPS + DOCKERFILE_BUILD_DEBIAN_DEPS
|
||||
)
|
||||
elif self.distro == "ubuntu" and self.version in ("22.04", "jammy"):
|
||||
# Ubuntu Jammy requires a more up-to-date conmon
|
||||
# package (see https://github.com/freedomofpress/dangerzone/issues/685)
|
||||
install_deps = DOCKERFILE_CONMON_UPDATE + DOCKERFILE_BUILD_DEBIAN_DEPS
|
||||
|
@ -613,8 +655,6 @@ class Env:
|
|||
"noble",
|
||||
"24.10",
|
||||
"ocular",
|
||||
"25.04",
|
||||
"plucky",
|
||||
):
|
||||
install_deps = DOCKERFILE_UBUNTU_REM_USER + DOCKERFILE_BUILD_DEBIAN_DEPS
|
||||
package_pattern = f"dangerzone_{version}-*_*.deb"
|
||||
|
|
|
@ -251,6 +251,29 @@ Install dependencies:
|
|||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:memo: Expand this section if you are on Ubuntu 20.04 (Focal).</i></summary>
|
||||
</br>
|
||||
|
||||
The default Python version that ships with Ubuntu Focal (3.8) is not
|
||||
compatible with PySide6, which requires Python 3.9 or greater.
|
||||
|
||||
You can install Python 3.9 using the `python3.9` package.
|
||||
|
||||
```bash
|
||||
sudo apt install -y python3.9
|
||||
```
|
||||
|
||||
Poetry will automatically pick up the correct version when running.
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
```sh
|
||||
sudo apt install -y podman dh-python build-essential make libqt6gui6 \
|
||||
pipx python3 python3-dev
|
||||
|
@ -327,6 +350,28 @@ sudo dnf install -y rpm-build podman python3 python3-devel python3-poetry-core \
|
|||
pipx qt6-qtbase-gui
|
||||
```
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<details>
|
||||
<summary><i>:memo: Expand this section if you are on Fedora 41.</i></summary>
|
||||
</br>
|
||||
|
||||
The default Python version that ships with Fedora 41 (3.13) is not
|
||||
compatible with PySide6, which requires Python 3.12 or earlier.
|
||||
|
||||
You can install Python 3.12 using the `python3.12` package.
|
||||
|
||||
```bash
|
||||
sudo dnf install -y python3.12
|
||||
```
|
||||
|
||||
Poetry will automatically pick up the correct version when running.
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Install Poetry using `pipx`:
|
||||
|
||||
```sh
|
||||
|
@ -990,6 +1035,11 @@ class QADebianTrixie(QADebianBased):
|
|||
VERSION = "trixie"
|
||||
|
||||
|
||||
class QAUbuntu2004(QADebianBased):
|
||||
DISTRO = "ubuntu"
|
||||
VERSION = "20.04"
|
||||
|
||||
|
||||
class QAUbuntu2204(QADebianBased):
|
||||
DISTRO = "ubuntu"
|
||||
VERSION = "22.04"
|
||||
|
@ -1005,11 +1055,6 @@ class QAUbuntu2410(QADebianBased):
|
|||
VERSION = "24.10"
|
||||
|
||||
|
||||
class QAUbuntu2504(QADebianBased):
|
||||
DISTRO = "ubuntu"
|
||||
VERSION = "25.04"
|
||||
|
||||
|
||||
class QAFedora(QALinux):
|
||||
"""Base class for Fedora distros.
|
||||
|
||||
|
@ -1027,10 +1072,6 @@ class QAFedora(QALinux):
|
|||
)
|
||||
|
||||
|
||||
class QAFedora42(QAFedora):
|
||||
VERSION = "42"
|
||||
|
||||
|
||||
class QAFedora41(QAFedora):
|
||||
VERSION = "41"
|
||||
|
||||
|
|
|
@ -1,680 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MEDIA_TYPE_INDEX_V1_JSON = "application/vnd.oci.image.index.v1+json"
|
||||
MEDIA_TYPE_MANIFEST_V1_JSON = "application/vnd.oci.image.manifest.v1+json"
|
||||
|
||||
ENV_RUNTIME = "REPRO_RUNTIME"
|
||||
ENV_DATETIME = "REPRO_DATETIME"
|
||||
ENV_SDE = "REPRO_SOURCE_DATE_EPOCH"
|
||||
ENV_CACHE = "REPRO_CACHE"
|
||||
ENV_BUILDKIT = "REPRO_BUILDKIT_IMAGE"
|
||||
ENV_ROOTLESS = "REPRO_ROOTLESS"
|
||||
|
||||
DEFAULT_BUILDKIT_IMAGE = "moby/buildkit:v0.19.0@sha256:14aa1b4dd92ea0a4cd03a54d0c6079046ea98cd0c0ae6176bdd7036ba370cbbe"
|
||||
DEFAULT_BUILDKIT_IMAGE_ROOTLESS = "moby/buildkit:v0.19.0-rootless@sha256:e901cffdad753892a7c3afb8b9972549fca02c73888cf340c91ed801fdd96d71"
|
||||
|
||||
MSG_BUILD_CTX = """Build environment:
|
||||
- Container runtime: {runtime}
|
||||
- BuildKit image: {buildkit_image}
|
||||
- Rootless support: {rootless}
|
||||
- Caching enabled: {use_cache}
|
||||
- Build context: {context}
|
||||
- Dockerfile: {dockerfile}
|
||||
- Output: {output}
|
||||
|
||||
Build parameters:
|
||||
- SOURCE_DATE_EPOCH: {sde}
|
||||
- Build args: {build_args}
|
||||
- Tag: {tag}
|
||||
- Platform: {platform}
|
||||
|
||||
Podman-only arguments:
|
||||
- BuildKit arguments: {buildkit_args}
|
||||
|
||||
Docker-only arguments:
|
||||
- Docker Buildx arguments: {buildx_args}
|
||||
"""
|
||||
|
||||
|
||||
def pretty_error(obj: dict, msg: str):
|
||||
raise Exception(f"{msg}\n{pprint.pprint(obj)}")
|
||||
|
||||
|
||||
def get_key(obj: dict, key: str) -> object:
|
||||
if key not in obj:
|
||||
pretty_error(f"Could not find key '{key}' in the dictionary:", obj)
|
||||
return obj[key]
|
||||
|
||||
|
||||
def run(cmd, dry=False, check=True):
|
||||
action = "Would have run" if dry else "Running"
|
||||
logger.debug(f"{action}: {shlex.join(cmd)}")
|
||||
if not dry:
|
||||
subprocess.run(cmd, check=check)
|
||||
|
||||
|
||||
def snip_contents(contents: str, num: int) -> str:
|
||||
contents = contents.replace("\n", "")
|
||||
if len(contents) > num:
|
||||
return (
|
||||
contents[:num]
|
||||
+ f" [... {len(contents) - num} characters omitted."
|
||||
+ " Pass --show-contents to print them in their entirety]"
|
||||
)
|
||||
return contents
|
||||
|
||||
|
||||
def detect_container_runtime() -> str:
|
||||
"""Auto-detect the installed container runtime in the system."""
|
||||
if shutil.which("docker"):
|
||||
return "docker"
|
||||
elif shutil.which("podman"):
|
||||
return "podman"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def parse_runtime(args) -> str:
|
||||
if args.runtime is not None:
|
||||
return args.runtime
|
||||
|
||||
runtime = os.environ.get(ENV_RUNTIME)
|
||||
if runtime is None:
|
||||
raise RuntimeError("No container runtime detected in your system")
|
||||
if runtime not in ("docker", "podman"):
|
||||
raise RuntimeError(
|
||||
"Only 'docker' or 'podman' container runtimes"
|
||||
" are currently supported by this script"
|
||||
)
|
||||
|
||||
|
||||
def parse_use_cache(args) -> bool:
|
||||
if args.no_cache:
|
||||
return False
|
||||
return bool(int(os.environ.get(ENV_CACHE, "1")))
|
||||
|
||||
|
||||
def parse_rootless(args, runtime: str) -> bool:
|
||||
rootless = args.rootless or bool(int(os.environ.get(ENV_ROOTLESS, "0")))
|
||||
if runtime != "podman" and rootless:
|
||||
raise RuntimeError("Rootless mode is only supported with Podman runtime")
|
||||
return rootless
|
||||
|
||||
|
||||
def parse_sde(args) -> str:
|
||||
sde = os.environ.get(ENV_SDE, args.source_date_epoch)
|
||||
dt = os.environ.get(ENV_DATETIME, args.datetime)
|
||||
|
||||
if (sde is not None and dt is not None) or (sde is None and dt is None):
|
||||
raise RuntimeError("You need to pass either a source date epoch or a datetime")
|
||||
|
||||
if sde is not None:
|
||||
return str(sde)
|
||||
|
||||
if dt is not None:
|
||||
d = datetime.datetime.fromisoformat(dt)
|
||||
# If the datetime is naive, assume its timezone is UTC. The check is
|
||||
# taken from:
|
||||
# https://docs.python.org/3/library/datetime.html#determining-if-an-object-is-aware-or-naive
|
||||
if d.tzinfo is None or d.tzinfo.utcoffset(d) is None:
|
||||
d = d.replace(tzinfo=datetime.timezone.utc)
|
||||
return int(d.timestamp())
|
||||
|
||||
|
||||
def parse_buildkit_image(args, rootless: bool, runtime: str) -> str:
|
||||
default = DEFAULT_BUILDKIT_IMAGE_ROOTLESS if rootless else DEFAULT_BUILDKIT_IMAGE
|
||||
img = args.buildkit_image or os.environ.get(ENV_BUILDKIT, default)
|
||||
|
||||
if runtime == "podman" and not img.startswith("docker.io/"):
|
||||
img = "docker.io/" + img
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def parse_build_args(args) -> str:
|
||||
return args.build_arg or []
|
||||
|
||||
|
||||
def parse_buildkit_args(args, runtime: str) -> str:
|
||||
if not args.buildkit_args:
|
||||
return []
|
||||
|
||||
if runtime != "podman":
|
||||
raise RuntimeError("Cannot specify BuildKit arguments using the Podman runtime")
|
||||
|
||||
return shlex.split(args.buildkit_args)
|
||||
|
||||
|
||||
def parse_buildx_args(args, runtime: str) -> str:
|
||||
if not args.buildx_args:
|
||||
return []
|
||||
|
||||
if runtime != "docker":
|
||||
raise RuntimeError(
|
||||
"Cannot specify Docker Buildx arguments using the Podman runtime"
|
||||
)
|
||||
|
||||
return shlex.split(args.buildx_args)
|
||||
|
||||
|
||||
def parse_image_digest(args) -> str | None:
|
||||
if not args.expected_image_digest:
|
||||
return None
|
||||
parsed = args.expected_image_digest.split(":", 1)
|
||||
if len(parsed) == 1:
|
||||
return parsed[0]
|
||||
else:
|
||||
return parsed[1]
|
||||
|
||||
|
||||
def parse_path(path: str | None) -> str | None:
|
||||
return path and str(Path(path).absolute())
|
||||
|
||||
|
||||
##########################
|
||||
# OCI parsing logic
|
||||
#
|
||||
# Compatible with:
|
||||
# * https://github.com/opencontainers/image-spec/blob/main/image-layout.md
|
||||
|
||||
|
||||
def oci_print_info(parsed: dict, full: bool) -> None:
|
||||
print(f"The OCI tarball contains an index and {len(parsed) - 1} manifest(s):")
|
||||
print()
|
||||
print(f"Image digest: {parsed[1]['digest']}")
|
||||
for i, info in enumerate(parsed):
|
||||
print()
|
||||
if i == 0:
|
||||
print(f"Index ({info['path']}):")
|
||||
else:
|
||||
print(f"Manifest {i} ({info['path']}):")
|
||||
print(f" Digest: {info['digest']}")
|
||||
print(f" Media type: {info['media_type']}")
|
||||
print(f" Platform: {info['platform'] or '-'}")
|
||||
contents = info["contents"] if full else snip_contents(info["contents"], 600)
|
||||
print(f" Contents: {contents}")
|
||||
print()
|
||||
|
||||
|
||||
def oci_normalize_path(path):
|
||||
if path.startswith("sha256:"):
|
||||
hash_algo, checksum = path.split(":")
|
||||
path = f"blobs/{hash_algo}/{checksum}"
|
||||
return path
|
||||
|
||||
|
||||
def oci_get_file_from_tarball(tar: tarfile.TarFile, path: str) -> dict:
|
||||
"""Get file from an OCI tarball.
|
||||
|
||||
If the filename cannot be found, search again by prefixing it with "./", since we
|
||||
have encountered path names in OCI tarballs prefixed with "./".
|
||||
"""
|
||||
try:
|
||||
return tar.extractfile(path).read().decode()
|
||||
except KeyError:
|
||||
if not path.startswith("./") and not path.startswith("/"):
|
||||
path = "./" + path
|
||||
try:
|
||||
return tar.extractfile(path).read().decode()
|
||||
except KeyError:
|
||||
# Do not raise here, so that we can raise the original exception below.
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
def oci_parse_manifest(tar: tarfile.TarFile, path: str, platform: dict | None) -> dict:
|
||||
"""Parse manifest information in JSON format.
|
||||
|
||||
Interestingly, the platform info for a manifest is not included in the
|
||||
manifest itself, but in the descriptor that points to it. So, we have to
|
||||
carry it from the previous manifest and include in the info here.
|
||||
"""
|
||||
path = oci_normalize_path(path)
|
||||
contents = oci_get_file_from_tarball(tar, path)
|
||||
digest = "sha256:" + hashlib.sha256(contents.encode()).hexdigest()
|
||||
contents_dict = json.loads(contents)
|
||||
media_type = get_key(contents_dict, "mediaType")
|
||||
manifests = contents_dict.get("manifests", [])
|
||||
|
||||
if platform:
|
||||
os = get_key(platform, "os")
|
||||
arch = get_key(platform, "architecture")
|
||||
platform = f"{os}/{arch}"
|
||||
|
||||
return {
|
||||
"path": path,
|
||||
"contents": contents,
|
||||
"digest": digest,
|
||||
"media_type": media_type,
|
||||
"platform": platform,
|
||||
"manifests": manifests,
|
||||
}
|
||||
|
||||
|
||||
def oci_parse_manifests_dfs(
|
||||
tar: tarfile.TarFile, path: str, parsed: list, platform: dict | None = None
|
||||
) -> None:
|
||||
info = oci_parse_manifest(tar, path, platform)
|
||||
parsed.append(info)
|
||||
for m in info["manifests"]:
|
||||
oci_parse_manifests_dfs(tar, m["digest"], parsed, m.get("platform"))
|
||||
|
||||
|
||||
def oci_parse_tarball(path: Path) -> dict:
|
||||
parsed = []
|
||||
with tarfile.TarFile.open(path) as tar:
|
||||
oci_parse_manifests_dfs(tar, "index.json", parsed)
|
||||
return parsed
|
||||
|
||||
|
||||
##########################
|
||||
# Image building logic
|
||||
|
||||
|
||||
def podman_build(
|
||||
context: str,
|
||||
dockerfile: str | None,
|
||||
tag: str | None,
|
||||
buildkit_image: str,
|
||||
sde: int,
|
||||
rootless: bool,
|
||||
use_cache: bool,
|
||||
output: Path,
|
||||
build_args: list,
|
||||
platform: str,
|
||||
buildkit_args: list,
|
||||
dry: bool,
|
||||
):
|
||||
rootless_args = []
|
||||
rootful_args = []
|
||||
if rootless:
|
||||
rootless_args = [
|
||||
"--userns",
|
||||
"keep-id:uid=1000,gid=1000",
|
||||
"--security-opt",
|
||||
"seccomp=unconfined",
|
||||
"--security-opt",
|
||||
"apparmor=unconfined",
|
||||
"-e",
|
||||
"BUILDKITD_FLAGS=--oci-worker-no-process-sandbox",
|
||||
]
|
||||
else:
|
||||
rootful_args = ["--privileged"]
|
||||
|
||||
dockerfile_args_podman = []
|
||||
dockerfile_args_buildkit = []
|
||||
if dockerfile:
|
||||
dockerfile_args_podman = ["-v", f"{dockerfile}:/tmp/Dockerfile"]
|
||||
dockerfile_args_buildkit = ["--local", "dockerfile=/tmp"]
|
||||
else:
|
||||
dockerfile_args_buildkit = ["--local", "dockerfile=/tmp/work"]
|
||||
|
||||
tag_args = f",name={tag}" if tag else ""
|
||||
|
||||
cache_args = []
|
||||
if use_cache:
|
||||
cache_args = [
|
||||
"--export-cache",
|
||||
"type=local,mode=max,dest=/tmp/cache",
|
||||
"--import-cache",
|
||||
"type=local,src=/tmp/cache",
|
||||
]
|
||||
|
||||
_build_args = []
|
||||
for arg in build_args:
|
||||
_build_args.append("--opt")
|
||||
_build_args.append(f"build-arg:{arg}")
|
||||
platform_args = ["--opt", f"platform={platform}"] if platform else []
|
||||
|
||||
cmd = [
|
||||
"podman",
|
||||
"run",
|
||||
"-it",
|
||||
"--rm",
|
||||
"-v",
|
||||
"buildkit_cache:/tmp/cache",
|
||||
"-v",
|
||||
f"{output.parent}:/tmp/image",
|
||||
"-v",
|
||||
f"{context}:/tmp/work",
|
||||
"--entrypoint",
|
||||
"buildctl-daemonless.sh",
|
||||
*rootless_args,
|
||||
*rootful_args,
|
||||
*dockerfile_args_podman,
|
||||
buildkit_image,
|
||||
"build",
|
||||
"--frontend",
|
||||
"dockerfile.v0",
|
||||
"--local",
|
||||
"context=/tmp/work",
|
||||
"--opt",
|
||||
f"build-arg:SOURCE_DATE_EPOCH={sde}",
|
||||
*_build_args,
|
||||
"--output",
|
||||
f"type=docker,dest=/tmp/image/{output.name},rewrite-timestamp=true{tag_args}",
|
||||
*cache_args,
|
||||
*dockerfile_args_buildkit,
|
||||
*platform_args,
|
||||
*buildkit_args,
|
||||
]
|
||||
|
||||
run(cmd, dry)
|
||||
|
||||
|
||||
def docker_build(
|
||||
context: str,
|
||||
dockerfile: str | None,
|
||||
tag: str | None,
|
||||
buildkit_image: str,
|
||||
sde: int,
|
||||
use_cache: bool,
|
||||
output: Path,
|
||||
build_args: list,
|
||||
platform: str,
|
||||
buildx_args: list,
|
||||
dry: bool,
|
||||
):
|
||||
builder_id = hashlib.sha256(buildkit_image.encode()).hexdigest()
|
||||
builder_name = f"repro-build-{builder_id}"
|
||||
tag_args = ["-t", tag] if tag else []
|
||||
cache_args = [] if use_cache else ["--no-cache", "--pull"]
|
||||
|
||||
cmd = [
|
||||
"docker",
|
||||
"buildx",
|
||||
"create",
|
||||
"--name",
|
||||
builder_name,
|
||||
"--driver-opt",
|
||||
f"image={buildkit_image}",
|
||||
]
|
||||
run(cmd, dry, check=False)
|
||||
|
||||
dockerfile_args = ["-f", dockerfile] if dockerfile else []
|
||||
_build_args = []
|
||||
for arg in build_args:
|
||||
_build_args.append("--build-arg")
|
||||
_build_args.append(arg)
|
||||
platform_args = ["--platform", platform] if platform else []
|
||||
|
||||
cmd = [
|
||||
"docker",
|
||||
"buildx",
|
||||
"--builder",
|
||||
builder_name,
|
||||
"build",
|
||||
"--build-arg",
|
||||
f"SOURCE_DATE_EPOCH={sde}",
|
||||
*_build_args,
|
||||
"--provenance",
|
||||
"false",
|
||||
"--output",
|
||||
f"type=docker,dest={output},rewrite-timestamp=true",
|
||||
*cache_args,
|
||||
*tag_args,
|
||||
*dockerfile_args,
|
||||
*platform_args,
|
||||
*buildx_args,
|
||||
context,
|
||||
]
|
||||
run(cmd, dry)
|
||||
|
||||
|
||||
##########################
|
||||
# Command logic
|
||||
|
||||
|
||||
def build(args):
|
||||
runtime = parse_runtime(args)
|
||||
use_cache = parse_use_cache(args)
|
||||
sde = parse_sde(args)
|
||||
rootless = parse_rootless(args, runtime)
|
||||
buildkit_image = parse_buildkit_image(args, rootless, runtime)
|
||||
build_args = parse_build_args(args)
|
||||
platform = args.platform
|
||||
buildkit_args = parse_buildkit_args(args, runtime)
|
||||
buildx_args = parse_buildx_args(args, runtime)
|
||||
tag = args.tag
|
||||
dockerfile = parse_path(args.file)
|
||||
output = Path(parse_path(args.output))
|
||||
dry = args.dry
|
||||
context = parse_path(args.context)
|
||||
|
||||
logger.info(
|
||||
MSG_BUILD_CTX.format(
|
||||
runtime=runtime,
|
||||
buildkit_image=buildkit_image,
|
||||
sde=sde,
|
||||
rootless=rootless,
|
||||
use_cache=use_cache,
|
||||
context=context,
|
||||
dockerfile=dockerfile or "(not provided)",
|
||||
tag=tag or "(not provided)",
|
||||
output=output,
|
||||
build_args=",".join(build_args) or "(not provided)",
|
||||
platform=platform or "(default)",
|
||||
buildkit_args=" ".join(buildkit_args) or "(not provided)",
|
||||
buildx_args=" ".join(buildx_args) or "(not provided)",
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
if runtime == "docker":
|
||||
docker_build(
|
||||
context,
|
||||
dockerfile,
|
||||
tag,
|
||||
buildkit_image,
|
||||
sde,
|
||||
use_cache,
|
||||
output,
|
||||
build_args,
|
||||
platform,
|
||||
buildx_args,
|
||||
dry,
|
||||
)
|
||||
else:
|
||||
podman_build(
|
||||
context,
|
||||
dockerfile,
|
||||
tag,
|
||||
buildkit_image,
|
||||
sde,
|
||||
rootless,
|
||||
use_cache,
|
||||
output,
|
||||
build_args,
|
||||
platform,
|
||||
buildkit_args,
|
||||
dry,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Failed with {e.returncode}")
|
||||
sys.exit(e.returncode)
|
||||
|
||||
|
||||
def analyze(args) -> None:
|
||||
expected_image_digest = parse_image_digest(args)
|
||||
tarball_path = Path(args.tarball)
|
||||
|
||||
parsed = oci_parse_tarball(tarball_path)
|
||||
oci_print_info(parsed, args.show_contents)
|
||||
|
||||
if expected_image_digest:
|
||||
cur_digest = parsed[1]["digest"].split(":")[1]
|
||||
if cur_digest != expected_image_digest:
|
||||
raise Exception(
|
||||
f"The image does not have the expected digest: {cur_digest} != {expected_image_digest}"
|
||||
)
|
||||
print(f"✅ Image digest matches {expected_image_digest}")
|
||||
|
||||
|
||||
def define_build_cmd_args(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"--runtime",
|
||||
choices=["docker", "podman"],
|
||||
default=detect_container_runtime(),
|
||||
help="The container runtime for building the image (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--datetime",
|
||||
metavar="YYYY-MM-DD",
|
||||
default=None,
|
||||
help=(
|
||||
"Provide a date and (optionally) a time in ISO format, which will"
|
||||
" be used as the timestamp of the image layers"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--buildkit-image",
|
||||
metavar="NAME:TAG@DIGEST",
|
||||
default=None,
|
||||
help=(
|
||||
"The BuildKit container image which will be used for building the"
|
||||
" reproducible container image. Make sure to pass the '-rootless'"
|
||||
" variant if you are using rootless Podman"
|
||||
" (default: docker.io/moby/buildkit:v0.19.0)"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source-date-epoch",
|
||||
"--sde",
|
||||
metavar="SECONDS",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Provide a Unix timestamp for the image layers",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-cache",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Do not use existing cached images for the container build. Build from the start with a new set of cached layers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rootless",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Run BuildKit in rootless mode (Podman only)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
metavar="FILE",
|
||||
default=None,
|
||||
help="Pathname of a Dockerfile",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
metavar="FILE",
|
||||
default=Path.cwd() / "image.tar",
|
||||
help="Path to save OCI tarball (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--tag",
|
||||
metavar="TAG",
|
||||
default=None,
|
||||
help="Tag the built image with the name %(metavar)s",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-arg",
|
||||
metavar="ARG=VALUE",
|
||||
action="append",
|
||||
default=None,
|
||||
help="Set build-time variables",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
metavar="PLAT1,PLAT2",
|
||||
default=None,
|
||||
help="Set platform for the image",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--buildkit-args",
|
||||
metavar="'ARG1 ARG2'",
|
||||
default=None,
|
||||
help="Extra arguments for BuildKit (Podman only)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--buildx-args",
|
||||
metavar="'ARG1 ARG2'",
|
||||
default=None,
|
||||
help="Extra arguments for Docker Buildx (Docker only)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Do not run any commands, just print what would happen",
|
||||
)
|
||||
parser.add_argument(
|
||||
"context",
|
||||
metavar="CONTEXT",
|
||||
help="Path to the build context",
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> dict:
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||
|
||||
build_parser = subparsers.add_parser("build", help="Perform a build operation")
|
||||
build_parser.set_defaults(func=build)
|
||||
define_build_cmd_args(build_parser)
|
||||
|
||||
analyze_parser = subparsers.add_parser("analyze", help="Analyze an OCI tarball")
|
||||
analyze_parser.set_defaults(func=analyze)
|
||||
analyze_parser.add_argument(
|
||||
"tarball",
|
||||
metavar="FILE",
|
||||
help="Path to OCI image in .tar format",
|
||||
)
|
||||
analyze_parser.add_argument(
|
||||
"--expected-image-digest",
|
||||
metavar="DIGEST",
|
||||
default=None,
|
||||
help="The expected digest for the provided image",
|
||||
)
|
||||
analyze_parser.add_argument(
|
||||
"--show-contents",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Show full file contents",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
args = parse_args()
|
||||
|
||||
if not hasattr(args, "func"):
|
||||
args.func = build
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
|
@ -4,7 +4,6 @@ import argparse
|
|||
import hashlib
|
||||
import logging
|
||||
import pathlib
|
||||
import platform
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
|
@ -12,72 +11,131 @@ import urllib.request
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if platform.system() in ["Darwin", "Windows"]:
|
||||
CONTAINER_RUNTIME = "docker"
|
||||
elif platform.system() == "Linux":
|
||||
CONTAINER_RUNTIME = "podman"
|
||||
DIFFOCI_URL = "https://github.com/reproducible-containers/diffoci/releases/download/v0.1.5/diffoci-v0.1.5.linux-amd64"
|
||||
DIFFOCI_CHECKSUM = "01d25fe690196945a6bd510d30559338aa489c034d3a1b895a0d82a4b860698f"
|
||||
DIFFOCI_PATH = (
|
||||
pathlib.Path.home() / ".local" / "share" / "dangerzone-dev" / "helpers" / "diffoci"
|
||||
)
|
||||
IMAGE_NAME = "dangerzone.rocks/dangerzone"
|
||||
|
||||
|
||||
def run(*args):
|
||||
"""Simple function that runs a command and checks the result."""
|
||||
"""Simple function that runs a command, validates it, and returns the output"""
|
||||
logger.debug(f"Running command: {' '.join(args)}")
|
||||
return subprocess.run(args, check=True)
|
||||
return subprocess.run(
|
||||
args,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout
|
||||
|
||||
|
||||
def build_image(
|
||||
platform=None,
|
||||
runtime=None,
|
||||
cache=True,
|
||||
date=None,
|
||||
):
|
||||
def git_commit_get():
|
||||
return run("git", "rev-parse", "--short", "HEAD").decode().strip()
|
||||
|
||||
|
||||
def git_determine_tag():
|
||||
return run("git", "describe", "--long", "--first-parent").decode().strip()[1:]
|
||||
|
||||
|
||||
def git_verify(commit, source):
|
||||
if not commit in source:
|
||||
raise RuntimeError(
|
||||
f"Image '{source}' does not seem to be built from commit '{commit}'"
|
||||
)
|
||||
|
||||
|
||||
def diffoci_hash_matches(diffoci):
|
||||
"""Check if the hash of the downloaded diffoci bin matches the expected one."""
|
||||
m = hashlib.sha256()
|
||||
m.update(diffoci)
|
||||
diffoci_checksum = m.hexdigest()
|
||||
return diffoci_checksum == DIFFOCI_CHECKSUM
|
||||
|
||||
|
||||
def diffoci_is_installed():
|
||||
"""Determine if diffoci has been installed.
|
||||
|
||||
Determine if diffoci has been installed, by checking if the binary exists, and if
|
||||
its hash is the expected one. If the binary exists but the hash is different, then
|
||||
this is a sign that we need to update the local diffoci binary.
|
||||
"""
|
||||
if not DIFFOCI_PATH.exists():
|
||||
return False
|
||||
return diffoci_hash_matches(DIFFOCI_PATH.open("rb").read())
|
||||
|
||||
|
||||
def diffoci_download():
|
||||
"""Download the diffoci tool, based on a URL and its checksum."""
|
||||
with urllib.request.urlopen(DIFFOCI_URL) as f:
|
||||
diffoci_bin = f.read()
|
||||
|
||||
if not diffoci_hash_matches(diffoci_bin):
|
||||
raise ValueError(
|
||||
"Unexpected checksum for downloaded diffoci binary:"
|
||||
f" {diffoci_checksum} !={DIFFOCI_CHECKSUM}"
|
||||
)
|
||||
|
||||
DIFFOCI_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
DIFFOCI_PATH.open("wb+").write(diffoci_bin)
|
||||
DIFFOCI_PATH.chmod(DIFFOCI_PATH.stat().st_mode | stat.S_IEXEC)
|
||||
|
||||
|
||||
def diffoci_diff(source, local_target):
|
||||
"""Diff the source image against the recently built target image using diffoci."""
|
||||
target = f"podman://{local_target}"
|
||||
try:
|
||||
return run(
|
||||
str(DIFFOCI_PATH),
|
||||
"diff",
|
||||
source,
|
||||
target,
|
||||
"--semantic",
|
||||
"--verbose",
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
error = e.stdout.decode()
|
||||
raise RuntimeError(
|
||||
f"Could not rebuild an identical image to {source}. Diffoci report:\n{error}"
|
||||
)
|
||||
|
||||
|
||||
def build_image(tag, use_cache=False):
|
||||
"""Build the Dangerzone container image with a special tag."""
|
||||
platform_args = [] if not platform else ["--platform", platform]
|
||||
runtime_args = [] if not runtime else ["--runtime", runtime]
|
||||
cache_args = [] if cache else ["--use-cache", "no"]
|
||||
date_args = [] if not date else ["--debian-archive-date", date]
|
||||
run(
|
||||
"python3",
|
||||
"./install/common/build-image.py",
|
||||
*platform_args,
|
||||
*runtime_args,
|
||||
*cache_args,
|
||||
*date_args,
|
||||
"--no-save",
|
||||
"--use-cache",
|
||||
str(use_cache),
|
||||
"--tag",
|
||||
tag,
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
image_tag = git_determine_tag()
|
||||
# TODO: Remove the local "podman://" prefix once we have started pushing images to a
|
||||
# remote.
|
||||
default_image_name = f"podman://{IMAGE_NAME}:{image_tag}"
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=sys.argv[0],
|
||||
description="Dev script for verifying container image reproducibility",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
default=None,
|
||||
help=f"The platform for building the image (default: current platform)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--runtime",
|
||||
choices=["docker", "podman"],
|
||||
default=CONTAINER_RUNTIME,
|
||||
help=f"The container runtime for building the image (default: {CONTAINER_RUNTIME})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-cache",
|
||||
default=False,
|
||||
action="store_true",
|
||||
"--source",
|
||||
default=default_image_name,
|
||||
help=(
|
||||
"Do not use existing cached images for the container build."
|
||||
" Build from the start with a new set of cached layers."
|
||||
"The name of the image that you want to reproduce. If the image resides in"
|
||||
" the local Docker / Podman engine, you can prefix it with podman:// or"
|
||||
f" docker:// accordingly (default: {default_image_name})"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debian-archive-date",
|
||||
default=None,
|
||||
help="Use a specific Debian snapshot archive, by its date",
|
||||
)
|
||||
parser.add_argument(
|
||||
"digest",
|
||||
help="The digest of the image that you want to reproduce",
|
||||
"--use-cache",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Whether to reuse the build cache (off by default for better reproducibility)",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
@ -90,25 +148,32 @@ def main():
|
|||
)
|
||||
args = parse_args()
|
||||
|
||||
logger.info(f"Building container image")
|
||||
build_image(
|
||||
args.platform,
|
||||
args.runtime,
|
||||
not args.no_cache,
|
||||
args.debian_archive_date,
|
||||
)
|
||||
logger.info(f"Ensuring that current Git commit matches image '{args.source}'")
|
||||
commit = git_commit_get()
|
||||
git_verify(commit, args.source)
|
||||
|
||||
if not diffoci_is_installed():
|
||||
logger.info(f"Downloading diffoci helper from {DIFFOCI_URL}")
|
||||
diffoci_download()
|
||||
|
||||
tag = f"reproduce-{commit}"
|
||||
target = f"{IMAGE_NAME}:{tag}"
|
||||
logger.info(f"Building container image and tagging it as '{target}'")
|
||||
build_image(tag, args.use_cache)
|
||||
|
||||
logger.info(
|
||||
f"Check that the reproduced image has the expected digest: {args.digest}"
|
||||
)
|
||||
run(
|
||||
"./dev_scripts/repro-build.py",
|
||||
"analyze",
|
||||
"--show-contents",
|
||||
"share/container.tar",
|
||||
"--expected-image-digest",
|
||||
args.digest,
|
||||
f"Ensuring that source image '{args.source}' is semantically identical with"
|
||||
f" built image '{target}'"
|
||||
)
|
||||
try:
|
||||
diffoci_diff(args.source, target)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(
|
||||
f"Could not reproduce image {args.source} for commit {commit}"
|
||||
)
|
||||
breakpoint()
|
||||
|
||||
logger.info(f"Successfully reproduced image '{args.source}' from commit '{commit}'")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -11,8 +11,8 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
DZ_ASSETS = [
|
||||
"container-{version}-i686.tar",
|
||||
"container-{version}-arm64.tar",
|
||||
"container-{version}-i686.tar.gz",
|
||||
"container-{version}-arm64.tar.gz",
|
||||
"Dangerzone-{version}.msi",
|
||||
"Dangerzone-{version}-arm64.dmg",
|
||||
"Dangerzone-{version}-i686.dmg",
|
||||
|
|
|
@ -47,21 +47,21 @@ trigger a CI error.
|
|||
|
||||
For a simple way to reproduce a Dangerzone container image, you can checkout the
|
||||
commit this image was built from (you can find it from the image tag in its
|
||||
`g<commit>` portion), retrieve the date it was built (also included in the image
|
||||
tag), and run the following command in any environment:
|
||||
`g<commit>` portion), and run the following command in a Linux environment:
|
||||
|
||||
```
|
||||
./dev_scripts/reproduce-image.py \
|
||||
--debian-archive-date <date> \
|
||||
<digest>
|
||||
./dev_scripts/reproduce-image.py --source <image>
|
||||
```
|
||||
|
||||
where:
|
||||
* `<date>` should be given in YYYYMMDD format, e.g, 20250226
|
||||
* `<digest>` is the SHA-256 hash of the image for the **current platform**, with
|
||||
or without the `sha256:` prefix.
|
||||
This command will download the `diffoci` helper, build a container image from
|
||||
the current Git commit, and ensure that the built image matches the source one,
|
||||
with the exception of image names and file timestamps.
|
||||
|
||||
This command will build a container image from the current Git commit and the
|
||||
provided date for the Debian archives. Then, it will compare the digest of the
|
||||
manifest against the provided one. This is a simple way to ensure that the
|
||||
created image is bit-for-bit reproducible.
|
||||
> [!TIP]
|
||||
> If the source image is not pushed to a registry, and is local instead, you
|
||||
> can prefix it with `docker://` or `podman://` accordingly, so that `diffoci`
|
||||
> can load it from the local Docker / Podman container engine. For example:
|
||||
>
|
||||
> ```
|
||||
> ./dev_scripts/reproduce.py --source podman://dangerzone.rocks/dangerzone:0.8.0-125-g725ce3b
|
||||
> ```
|
||||
|
|
8
dodo.py
8
dodo.py
|
@ -9,7 +9,7 @@ from doit.action import CmdAction
|
|||
ARCH = "arm64" if platform.machine() == "arm64" else "i686"
|
||||
VERSION = open("share/version.txt").read().strip()
|
||||
FEDORA_VERSIONS = ["40", "41"]
|
||||
DEBIAN_VERSIONS = ["bullseye", "jammy", "mantic", "noble", "trixie"]
|
||||
DEBIAN_VERSIONS = ["bullseye", "focal", "jammy", "mantic", "noble", "trixie"]
|
||||
|
||||
### Global parameters
|
||||
|
||||
|
@ -57,7 +57,7 @@ IMAGE_DEPS = [
|
|||
*list_files("dangerzone/container_helpers"),
|
||||
"install/common/build-image.py",
|
||||
]
|
||||
IMAGE_TARGETS = ["share/container.tar", "share/image-id.txt"]
|
||||
IMAGE_TARGETS = ["share/container.tar.gz", "share/image-id.txt"]
|
||||
|
||||
SOURCE_DEPS = [
|
||||
*list_files("assets"),
|
||||
|
@ -188,8 +188,8 @@ def task_download_tessdata():
|
|||
|
||||
def task_build_image():
|
||||
"""Build the container image using ./install/common/build-image.py"""
|
||||
img_src = "share/container.tar"
|
||||
img_dst = RELEASE_DIR / f"container-{VERSION}-{ARCH}.tar" # FIXME: Add arch
|
||||
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
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import argparse
|
||||
import gzip
|
||||
import platform
|
||||
import secrets
|
||||
import subprocess
|
||||
|
@ -12,6 +13,8 @@ if platform.system() in ["Darwin", "Windows"]:
|
|||
elif platform.system() == "Linux":
|
||||
CONTAINER_RUNTIME = "podman"
|
||||
|
||||
ARCH = platform.machine()
|
||||
|
||||
|
||||
def str2bool(v):
|
||||
if isinstance(v, bool):
|
||||
|
@ -47,16 +50,6 @@ def determine_git_tag():
|
|||
)
|
||||
|
||||
|
||||
def determine_debian_archive_date():
|
||||
"""Get the date of the Debian archive from Dockerfile.env."""
|
||||
for env in Path("Dockerfile.env").read_text().split("\n"):
|
||||
if env.startswith("DEBIAN_ARCHIVE_DATE"):
|
||||
return env.split("=")[1]
|
||||
raise Exception(
|
||||
"Could not find 'DEBIAN_ARCHIVE_DATE' build argument in Dockerfile.env"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
|
@ -66,15 +59,16 @@ def main():
|
|||
help=f"The container runtime for building the image (default: {CONTAINER_RUNTIME})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--platform",
|
||||
default=None,
|
||||
help=f"The platform for building the image (default: current platform)",
|
||||
"--no-save",
|
||||
action="store_true",
|
||||
help="Do not save the container image as a tarball in share/container.tar.gz",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
"-o",
|
||||
default=str(Path("share") / "container.tar"),
|
||||
help="Path to store the container image",
|
||||
"--compress-level",
|
||||
type=int,
|
||||
choices=range(0, 10),
|
||||
default=9,
|
||||
help="The Gzip compression level, from 0 (lowest) to 9 (highest, default)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use-cache",
|
||||
|
@ -89,62 +83,63 @@ def main():
|
|||
default=None,
|
||||
help="Provide a custom tag for the image (for development only)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debian-archive-date",
|
||||
"-d",
|
||||
default=determine_debian_archive_date(),
|
||||
help="Use a specific Debian snapshot archive, by its date (default %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Do not run any commands, just print what would happen",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
tag = args.tag or f"{args.debian_archive_date}-{determine_git_tag()}"
|
||||
image_name_tagged = f"{IMAGE_NAME}:{tag}"
|
||||
tarball_path = Path("share") / "container.tar.gz"
|
||||
image_id_path = Path("share") / "image-id.txt"
|
||||
|
||||
print(f"Building for architecture '{ARCH}'")
|
||||
|
||||
tag = args.tag or determine_git_tag()
|
||||
image_name_tagged = IMAGE_NAME + ":" + tag
|
||||
|
||||
print(f"Will tag the container image as '{image_name_tagged}'")
|
||||
image_id_path = Path("share") / "image-id.txt"
|
||||
if not args.dry:
|
||||
with open(image_id_path, "w") as f:
|
||||
f.write(tag)
|
||||
with open(image_id_path, "w") as f:
|
||||
f.write(tag)
|
||||
|
||||
# Build the container image, and tag it with the calculated tag
|
||||
print("Building container image")
|
||||
cache_args = [] if args.use_cache else ["--no-cache"]
|
||||
platform_args = [] if not args.platform else ["--platform", args.platform]
|
||||
rootless_args = [] if args.runtime == "docker" else ["--rootless"]
|
||||
rootless_args = []
|
||||
dry_args = [] if not args.dry else ["--dry"]
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"./dev_scripts/repro-build.py",
|
||||
"build",
|
||||
"--runtime",
|
||||
args.runtime,
|
||||
"--build-arg",
|
||||
f"DEBIAN_ARCHIVE_DATE={args.debian_archive_date}",
|
||||
"--datetime",
|
||||
args.debian_archive_date,
|
||||
*dry_args,
|
||||
"build",
|
||||
BUILD_CONTEXT,
|
||||
*cache_args,
|
||||
*platform_args,
|
||||
*rootless_args,
|
||||
"--tag",
|
||||
image_name_tagged,
|
||||
"--output",
|
||||
args.output,
|
||||
"-f",
|
||||
"Dockerfile",
|
||||
BUILD_CONTEXT,
|
||||
"--tag",
|
||||
image_name_tagged,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
if not args.no_save:
|
||||
print("Saving container image")
|
||||
cmd = subprocess.Popen(
|
||||
[
|
||||
CONTAINER_RUNTIME,
|
||||
"save",
|
||||
image_name_tagged,
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
print("Compressing container image")
|
||||
chunk_size = 4 << 20
|
||||
with gzip.open(
|
||||
tarball_path,
|
||||
"wb",
|
||||
compresslevel=args.compress_level,
|
||||
) as gzip_f:
|
||||
while True:
|
||||
chunk = cmd.stdout.read(chunk_size)
|
||||
if len(chunk) > 0:
|
||||
gzip_f.write(chunk)
|
||||
else:
|
||||
break
|
||||
cmd.wait(5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
|
@ -66,14 +66,14 @@ def build(build_dir, qubes=False):
|
|||
print("* Creating a Python sdist")
|
||||
tessdata = root / "share" / "tessdata"
|
||||
tessdata_bak = root / "tessdata.bak"
|
||||
container_tar = root / "share" / "container.tar"
|
||||
container_tar_bak = root / "container.tar.bak"
|
||||
container_tar_gz = root / "share" / "container.tar.gz"
|
||||
container_tar_gz_bak = root / "container.tar.gz.bak"
|
||||
|
||||
if tessdata.exists():
|
||||
tessdata.rename(tessdata_bak)
|
||||
stash_container = qubes and container_tar.exists()
|
||||
if stash_container and container_tar.exists():
|
||||
container_tar.rename(container_tar_bak)
|
||||
stash_container = qubes and container_tar_gz.exists()
|
||||
if stash_container and container_tar_gz.exists():
|
||||
container_tar_gz.rename(container_tar_gz_bak)
|
||||
try:
|
||||
subprocess.run(["poetry", "build", "-f", "sdist"], cwd=root, check=True)
|
||||
# Copy and unlink the Dangerzone sdist, instead of just renaming it. If the
|
||||
|
@ -84,8 +84,8 @@ def build(build_dir, qubes=False):
|
|||
finally:
|
||||
if tessdata_bak.exists():
|
||||
tessdata_bak.rename(tessdata)
|
||||
if stash_container and container_tar_bak.exists():
|
||||
container_tar_bak.rename(container_tar)
|
||||
if stash_container and container_tar_gz_bak.exists():
|
||||
container_tar_gz_bak.rename(container_tar_gz)
|
||||
|
||||
print("* Building RPM package")
|
||||
cmd = [
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
# * Qubes packages include some extra files under /etc/qubes-rpc, whereas
|
||||
# regular RPM packages include the container image under
|
||||
# /usr/share/container.tar
|
||||
# /usr/share/container.tar.gz
|
||||
# * Qubes packages have some extra dependencies.
|
||||
# 3. It is best to consume this SPEC file using the `install/linux/build-rpm.py`
|
||||
# script, which handles the necessary scaffolding for building the package.
|
||||
|
|
|
@ -31,6 +31,23 @@ def main():
|
|||
cmd = ["poetry", "export", "--only", "debian"]
|
||||
container_requirements_txt = subprocess.check_output(cmd)
|
||||
|
||||
# XXX: Hack for Ubuntu Focal.
|
||||
#
|
||||
# The `requirements.txt` file is generated from our `pyproject.toml` file, and thus
|
||||
# specifies that the minimum Python version is 3.9. This was to accommodate to
|
||||
# PySide6, which is installed in macOS / Windows via `poetry` and works with Python
|
||||
# 3.9+. [1]
|
||||
#
|
||||
# The Python version in Ubuntu Focal though is 3.8. This generally was not much of
|
||||
# an issue, since we used the package manager to install dependencies. However, it
|
||||
# becomes an issue when we want to vendor the PyMuPDF package, using `pip`. In order
|
||||
# to sidestep this virtual limitation, we can just change the Python version in the
|
||||
# generated `requirements.txt` file in Ubuntu Focal from 3.9 to 3.8.
|
||||
#
|
||||
# [1] https://github.com/freedomofpress/dangerzone/pull/818
|
||||
if sys.version.startswith("3.8"):
|
||||
container_requirements_txt = container_requirements_txt.replace(b"3.9", b"3.8")
|
||||
|
||||
logger.info(f"Vendoring PyMuPDF under '{args.dest}'")
|
||||
# We prefer to call the CLI version of `pip`, instead of importing it directly, as
|
||||
# instructed here:
|
||||
|
|
40
install/linux/install-podman-ubuntu-focal.sh
Executable file
40
install/linux/install-podman-ubuntu-focal.sh
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Development script for installing Podman on Ubuntu Focal. Mainly to be used as
|
||||
# part of our CI pipelines, where we may install Podman on environments that
|
||||
# don't have sudo.
|
||||
|
||||
set -e
|
||||
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
provide() {
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install curl wget gnupg2 -y
|
||||
source /etc/os-release
|
||||
$SUDO sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' \
|
||||
> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- \
|
||||
| $SUDO apt-key add -
|
||||
$SUDO apt-get update -qq -y
|
||||
}
|
||||
|
||||
install() {
|
||||
$SUDO apt-get -qq --yes install podman
|
||||
podman --version
|
||||
}
|
||||
|
||||
if [[ "$1" == "--repo-only" ]]; then
|
||||
provide
|
||||
elif [[ "$1" == "" ]]; then
|
||||
provide
|
||||
install
|
||||
else
|
||||
echo "Unexpected argument: $1"
|
||||
echo "Usage: $0 [--repo-only]"
|
||||
exit 1
|
||||
fi
|
496
poetry.lock
generated
496
poetry.lock
generated
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "altgraph"
|
||||
|
@ -6,8 +6,6 @@ version = "0.17.4"
|
|||
description = "Python graph (network) package"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"darwin\""
|
||||
files = [
|
||||
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
|
||||
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||
|
@ -15,15 +13,13 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.9.0"
|
||||
version = "4.8.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
|
||||
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
|
||||
{file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"},
|
||||
{file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -33,8 +29,8 @@ sniffio = ">=1.1"
|
|||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
|
||||
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)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
|
@ -43,8 +39,6 @@ version = "2025.1.31"
|
|||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main", "dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
|
||||
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
|
||||
|
@ -56,8 +50,6 @@ version = "3.4.1"
|
|||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
|
||||
{file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
|
||||
|
@ -159,8 +151,6 @@ version = "8.1.8"
|
|||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main", "lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
||||
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
||||
|
@ -175,8 +165,6 @@ version = "3.1.1"
|
|||
description = "Pickler class to extend the standard pickle.Pickler functionality"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"},
|
||||
{file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"},
|
||||
|
@ -188,8 +176,6 @@ version = "0.4.6"
|
|||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main", "lint", "test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
|
@ -197,83 +183,81 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.7.0"
|
||||
version = "7.6.12"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "coverage-7.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a538a23119d1e2e2ce077e902d02ea3d8e0641786ef6e0faf11ce82324743944"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1586ad158523f4133499a4f322b230e2cfef9cc724820dbd58595a5a236186f4"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b6c96d69928a3a6767fab8dc1ce8a02cf0156836ccb1e820c7f45a423570d98"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f18d47641282664276977c604b5a261e51fefc2980f5271d547d706b06a837f"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a1e18a85bd066c7c556d85277a7adf4651f259b2579113844835ba1a74aafd"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:70f0925c4e2bfc965369f417e7cc72538fd1ba91639cf1e4ef4b1a6b50439b3b"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b0fac2088ec4aaeb5468b814bd3ff5e5978364bfbce5e567c44c9e2854469f6c"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3e212a894d8ae07fde2ca8b43d666a6d49bbbddb10da0f6a74ca7bd31f20054"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-win32.whl", hash = "sha256:f32b165bf6dfea0846a9c9c38b7e1d68f313956d60a15cde5d1709fddcaf3bee"},
|
||||
{file = "coverage-7.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:a2454b12a3f12cc4698f3508912e6225ec63682e2ca5a96f80a2b93cef9e63f3"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a0a207c87a9f743c8072d059b4711f8d13c456eb42dac778a7d2e5d4f3c253a7"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d673e3add00048215c2cc507f1228a7523fd8bf34f279ac98334c9b07bd2656"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f81fe93dc1b8e5673f33443c0786c14b77e36f1025973b85e07c70353e46882b"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8c7524779003d59948c51b4fcbf1ca4e27c26a7d75984f63488f3625c328b9b"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c124025430249118d018dcedc8b7426f39373527c845093132196f2a483b6dd"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f559c36d5cdc448ee13e7e56ed7b6b5d44a40a511d584d388a0f5d940977ba"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:37cbc7b0d93dfd133e33c7ec01123fbb90401dce174c3b6661d8d36fb1e30608"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7d2a65876274acf544703e943c010b60bd79404e3623a1e5d52b64a6e2728de5"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-win32.whl", hash = "sha256:f5a2f71d6a91238e7628f23538c26aa464d390cbdedf12ee2a7a0fb92a24482a"},
|
||||
{file = "coverage-7.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae8006772c6b0fa53c33747913473e064985dac4d65f77fd2fdc6474e7cd54e4"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:056d3017ed67e7ddf266e6f57378ece543755a4c9231e997789ab3bd11392c94"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33c1394d8407e2771547583b66a85d07ed441ff8fae5a4adb4237ad39ece60db"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fbb7a0c3c21908520149d7751cf5b74eb9b38b54d62997b1e9b3ac19a8ee2fe"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb356e7ae7c2da13f404bf8f75be90f743c6df8d4607022e759f5d7d89fe83f8"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce730d484038e97f27ea2dbe5d392ec5c2261f28c319a3bb266f6b213650135"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa4dff57fc21a575672176d5ab0ef15a927199e775c5e8a3d75162ab2b0c7705"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b667b91f4f714b17af2a18e220015c941d1cf8b07c17f2160033dbe1e64149f0"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:693d921621a0c8043bfdc61f7d4df5ea6d22165fe8b807cac21eb80dd94e4bbd"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-win32.whl", hash = "sha256:52fc89602cde411a4196c8c6894afb384f2125f34c031774f82a4f2608c59d7d"},
|
||||
{file = "coverage-7.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ce8cf59e09d31a4915ff4c3b94c6514af4c84b22c4cc8ad7c3c546a86150a92"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4545485fef7a8a2d8f30e6f79ce719eb154aab7e44217eb444c1d38239af2072"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1393e5aa9441dafb0162c36c8506c648b89aea9565b31f6bfa351e66c11bcd82"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:316f29cc3392fa3912493ee4c83afa4a0e2db04ff69600711f8c03997c39baaa"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ffde1d6bc2a92f9c9207d1ad808550873748ac2d4d923c815b866baa343b3f"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:416e2a8845eaff288f97eaf76ab40367deafb9073ffc47bf2a583f26b05e5265"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5efdeff5f353ed3352c04e6b318ab05c6ce9249c25ed3c2090c6e9cadda1e3b2"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:57f3bd0d29bf2bd9325c0ff9cc532a175110c4bf8f412c05b2405fd35745266d"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ab7090f04b12dc6469882ce81244572779d3a4b67eea1c96fb9ecc8c607ef39"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-win32.whl", hash = "sha256:180e3fc68ee4dc5af8b33b6ca4e3bb8aa1abe25eedcb958ba5cff7123071af68"},
|
||||
{file = "coverage-7.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:55143aa13c49491f5606f05b49ed88663446dce3a4d3c5d77baa4e36a16d3573"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc41374d2f27d81d6558f8a24e5c114580ffefc197fd43eabd7058182f743322"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:89078312f06237417adda7c021c33f80f7a6d2db8572a5f6c330d89b080061ce"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b2f144444879363ea8834cd7b6869d79ac796cb8f864b0cfdde50296cd95816"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60e6347d1ed882b1159ffea172cb8466ee46c665af4ca397edbf10ff53e9ffaf"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb203c0afffaf1a8f5b9659a013f8f16a1b2cad3a80a8733ceedc968c0cf4c57"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ad0edaa97cb983d9f2ff48cadddc3e1fb09f24aa558abeb4dc9a0dbacd12cbb4"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c5f8a5364fc37b2f172c26a038bc7ec4885f429de4a05fc10fdcb53fb5834c5c"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4e09534037933bf6eb31d804e72c52ec23219b32c1730f9152feabbd7499463"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-win32.whl", hash = "sha256:1b336d06af14f8da5b1f391e8dec03634daf54dfcb4d1c4fb6d04c09d83cef90"},
|
||||
{file = "coverage-7.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b54a1ee4c6f1905a436cbaa04b26626d27925a41cbc3a337e2d3ff7038187f07"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c8fbce80b2b8bf135d105aa8f5b36eae0c57d702a1cc3ebdea2a6f03f6cdde5"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d9710521f07f526de30ccdead67e6b236fe996d214e1a7fba8b36e2ba2cd8261"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7789e700f33f2b133adae582c9f437523cd5db8de845774988a58c360fc88253"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c36093aca722db73633cf2359026ed7782a239eb1c6db2abcff876012dc4cf"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c075d167a6ec99b798c1fdf6e391a1d5a2d054caffe9593ba0f97e3df2c04f0e"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d013c07061751ae81861cae6ec3a4fe04e84781b11fd4b6b4201590234b25c7b"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:104bf640f408f4e115b85110047c7f27377e1a8b7ba86f7db4fa47aa49dc9a8e"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:39abcacd1ed54e2c33c54bdc488b310e8ef6705833f7148b6eb9a547199d375d"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-win32.whl", hash = "sha256:8e336b56301774ace6be0017ff85c3566c556d938359b61b840796a0202f805c"},
|
||||
{file = "coverage-7.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:8c938c6ae59be67ac19a7204e079efc94b38222cd7d0269f96e45e18cddeaa59"},
|
||||
{file = "coverage-7.7.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:3b0e6e54591ae0d7427def8a4d40fca99df6b899d10354bab73cd5609807261c"},
|
||||
{file = "coverage-7.7.0-py3-none-any.whl", hash = "sha256:708f0a1105ef2b11c79ed54ed31f17e6325ac936501fc373f24be3e6a578146a"},
|
||||
{file = "coverage-7.7.0.tar.gz", hash = "sha256:cd879d4646055a573775a1cec863d00c9ff8c55860f8b17f6d8eee9140c06166"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"},
|
||||
{file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"},
|
||||
{file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"},
|
||||
{file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"},
|
||||
{file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"},
|
||||
{file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"},
|
||||
{file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"},
|
||||
{file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"},
|
||||
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "cx-freeze"
|
||||
|
@ -281,8 +265,6 @@ version = "7.2.10"
|
|||
description = "Create standalone executables from Python scripts"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "cx_Freeze-7.2.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:79dd0c1dc4d8d7369ea0288deccffcfed27a5ff3d11448fff5aa217b4ce83db2"},
|
||||
{file = "cx_Freeze-7.2.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a6e99c0ab5efb8303ba1f6f89df141c0b23f81cb18309981bd0bd6d03e75e1"},
|
||||
|
@ -341,8 +323,6 @@ version = "3.2.1"
|
|||
description = "Python and C interfaces for logging"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "cx_Logging-3.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e03e6ab69b7782b30eff481cabdb12b4f4693b684c8954af773b523e64f6211b"},
|
||||
{file = "cx_Logging-3.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710ca06a6cdd56a7c8e866c70e0a4dc92453e7000355b8dca07dac008ca57b6e"},
|
||||
|
@ -373,8 +353,6 @@ version = "0.36.0"
|
|||
description = "doit - Automation Tool"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"},
|
||||
{file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"},
|
||||
|
@ -385,7 +363,7 @@ cloudpickle = "*"
|
|||
importlib-metadata = ">=4.4"
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli ; python_version < \"3.11\""]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
|
@ -393,8 +371,6 @@ version = "1.2.2"
|
|||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev", "test"]
|
||||
markers = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.11\""
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
|
@ -409,8 +385,6 @@ version = "0.14.0"
|
|||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
|
@ -422,8 +396,6 @@ version = "1.0.7"
|
|||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
|
||||
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
|
||||
|
@ -445,8 +417,6 @@ version = "0.27.2"
|
|||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
|
||||
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
|
||||
|
@ -460,7 +430,7 @@ idna = "*"
|
|||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
|
@ -472,8 +442,6 @@ version = "3.10"
|
|||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main", "dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||
|
@ -488,49 +456,43 @@ version = "8.6.1"
|
|||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "package"]
|
||||
files = [
|
||||
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
|
||||
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
|
||||
]
|
||||
markers = {main = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.10\"", package = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""}
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=3.20"
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
perf = ["ipython"]
|
||||
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
|
||||
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
version = "3.1.5"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -545,8 +507,6 @@ version = "0.8.2"
|
|||
description = "A CLI interface to Jinja2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "jinja2-cli-0.8.2.tar.gz", hash = "sha256:a16bb1454111128e206f568c95938cdef5b5a139929378f72bb8cf6179e18e50"},
|
||||
{file = "jinja2_cli-0.8.2-py2.py3-none-any.whl", hash = "sha256:b91715c79496beaddad790171e7258a87db21c1a0b6d2b15bca3ba44b74aac5d"},
|
||||
|
@ -567,8 +527,6 @@ version = "0.16.3"
|
|||
description = "Library to instrument executable formats"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "lief-0.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0fca20122c27a86efb5d083fef6514fb2fbd910965654cb8568f2db8dfe2678f"},
|
||||
{file = "lief-0.16.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:17e78fc2790fd4ebd15cf9fd86abf0d7fa91aa229d70707f0bc0391ba522129c"},
|
||||
|
@ -620,8 +578,6 @@ version = "1.16.3"
|
|||
description = "Mach-O header analysis and editing"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"darwin\""
|
||||
files = [
|
||||
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
|
||||
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
|
||||
|
@ -636,8 +592,6 @@ version = "3.7"
|
|||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"},
|
||||
{file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"},
|
||||
|
@ -656,8 +610,6 @@ version = "3.0.2"
|
|||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||
|
@ -728,8 +680,6 @@ version = "1.15.0"
|
|||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
|
||||
|
@ -783,77 +733,17 @@ version = "1.0.0"
|
|||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.0.0"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"},
|
||||
{file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"},
|
||||
{file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"},
|
||||
{file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"},
|
||||
{file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"},
|
||||
{file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"},
|
||||
{file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"},
|
||||
{file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"},
|
||||
{file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"},
|
||||
{file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main", "package", "test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
|
@ -861,21 +751,19 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.7"
|
||||
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.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"},
|
||||
{file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"},
|
||||
{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.1.3)", "sphinx-autodoc-typehints (>=3)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
|
||||
type = ["mypy (>=1.14.1)"]
|
||||
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"
|
||||
|
@ -883,8 +771,6 @@ version = "1.5.0"
|
|||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
|
@ -900,8 +786,6 @@ version = "6.12.0"
|
|||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.8"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"darwin\""
|
||||
files = [
|
||||
{file = "pyinstaller-6.12.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:68f1e4cecf88a6272063977fa2a2c69ad37cf568e5901769d7206d0314c74f47"},
|
||||
{file = "pyinstaller-6.12.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fea76fc9b55ffa730fcf90beb897cce4399938460b0b6f40507fbebfc752c753"},
|
||||
|
@ -935,8 +819,6 @@ version = "2025.1"
|
|||
description = "Community maintained hooks for PyInstaller"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"darwin\""
|
||||
files = [
|
||||
{file = "pyinstaller_hooks_contrib-2025.1-py3-none-any.whl", hash = "sha256:d3c799470cbc0bda60dcc8e6b4ab976777532b77621337f2037f558905e3a8e9"},
|
||||
{file = "pyinstaller_hooks_contrib-2025.1.tar.gz", hash = "sha256:130818f9e9a0a7f2261f1fd66054966a3a50c99d000981c5d1db11d3ad0c6ab2"},
|
||||
|
@ -949,21 +831,19 @@ setuptools = ">=42.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "pymupdf"
|
||||
version = "1.25.4"
|
||||
version = "1.24.11"
|
||||
description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "debian"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b39ab5fc0cc793f2a93908cb4dc13182e61e9ace3476850ebf0e505f65355475"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:72ac2a460e15a4b75fb22d0bdeb9eb9b3bd6ab44d5288e56f85ff9e4241d0830"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bd16e264438b01bf6f6b8722deb49df20c0a9a7be2b4a8e29dcb7b5183d8b42a"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72817aa5f5023632346eee4aad0c32018d7879cf9a72e1820054dc9d8c48150f"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a8c23242d209512437187dc728001fc7c65c2dd9d37b50071f34c5cd745194a3"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-win32.whl", hash = "sha256:d90e6087f9b153999fa9f2c4709488fb0355de0302ec502a76c4fc0c0a0c535e"},
|
||||
{file = "pymupdf-1.25.4-cp39-abi3-win_amd64.whl", hash = "sha256:af8fa1f7920b3b27343b2511cb9fc44baf2bad8b948db0b0f9d5b2fa589cb305"},
|
||||
{file = "pymupdf-1.25.4.tar.gz", hash = "sha256:5f189466b68901055a9ddc77dc1c91cba081a60964f0caa6ff5b9b87001a0194"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:24c35ba9e731027ff24566b90d4986e9aac75e1ce47589b25de51e3c687ddb73"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:20c8eb65b855a33411246d6697a3f3166727fe2d8585753cf0db648730104be6"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32fd013e3c844f105c0a6a43ee82acc7cd0c900f6ff14f5eed9492840bbcbdd9"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2efb793644df99db0fe2468149048175cf25c5803997828efc9152aca838f5f2"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9b7ac5b8ec3daec17f2e830962ed091610e576a5e531d2fe28c437fbd69b1969"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-win32.whl", hash = "sha256:6fda6c7ed7e6ad74d9cfac5c3837ef42efd58c506440e2513a0a200bc3c4dbc0"},
|
||||
{file = "PyMuPDF-1.24.11-cp38-abi3-win_amd64.whl", hash = "sha256:745ce77532702d6ddeeecb47306d3669629aa5ff82708318cd652881f493b0ba"},
|
||||
{file = "PyMuPDF-1.24.11.tar.gz", hash = "sha256:6e45e57f14ac902029d4aacf07684958d0e58c769f47d9045b2048d0a3d20155"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -972,8 +852,6 @@ version = "6.8.2.1"
|
|||
description = "Python bindings for the Qt cross-platform application and UI framework"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "PySide6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3fcb551729f235475b2abe7d919027de54a65d850e744f60716f890202273720"},
|
||||
{file = "PySide6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:23d2a1a77b25459a049c4276b4e0bbfb375b73d3921061b1a16bcfa64e1fe517"},
|
||||
|
@ -992,8 +870,6 @@ version = "6.8.2.1"
|
|||
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "PySide6_Addons-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:5558816018042fecd0d782111ced529585a23ea9a010b518f8495764f578a01f"},
|
||||
{file = "PySide6_Addons-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f3d85e676851ada8238bc76ebfacbee738fc0b35b3bc15c9765dd107b8ee6ec4"},
|
||||
|
@ -1011,8 +887,6 @@ version = "6.8.2.1"
|
|||
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "PySide6_Essentials-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:ae5cc48f7e9a08e73e3ec2387ce245c8150e620b8d5a87548ebd4b8e3aeae49b"},
|
||||
{file = "PySide6_Essentials-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5ab31e5395a4724102edd6e8ff980fa3f7cde2aa79050763a1dcc30bb914195a"},
|
||||
|
@ -1029,8 +903,6 @@ version = "7.4.4"
|
|||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
|
||||
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
|
||||
|
@ -1053,8 +925,6 @@ version = "5.0.0"
|
|||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||
|
@ -1073,8 +943,6 @@ version = "3.14.0"
|
|||
description = "Thin-wrapper around the mock package for easier use with pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
|
||||
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
|
||||
|
@ -1092,8 +960,6 @@ version = "4.4.0"
|
|||
description = "pytest support for PyQt and PySide applications"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"},
|
||||
{file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"},
|
||||
|
@ -1113,8 +979,6 @@ version = "14.0"
|
|||
description = "pytest plugin to re-run tests to eliminate flaky failures"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest-rerunfailures-14.0.tar.gz", hash = "sha256:4a400bcbcd3c7a4ad151ab8afac123d90eca3abe27f98725dc4d9702887d2e92"},
|
||||
{file = "pytest_rerunfailures-14.0-py3-none-any.whl", hash = "sha256:4197bdd2eaeffdbf50b5ea6e7236f47ff0e44d1def8dae08e409f536d84e7b32"},
|
||||
|
@ -1130,8 +994,6 @@ version = "1.5.3"
|
|||
description = "A plugin to fake subprocess for pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "pytest_subprocess-1.5.3-py3-none-any.whl", hash = "sha256:b62580f5a84335fb9f2ec65d49e56a3c93f4722c148fe1771a002835d310a75b"},
|
||||
{file = "pytest_subprocess-1.5.3.tar.gz", hash = "sha256:c00b1140fb0211b3153e09500d770db10770baccbe6e05ee9c140036d1d811d5"},
|
||||
|
@ -1147,29 +1009,29 @@ test = ["Pygments (>=2.0)", "anyio", "docutils (>=0.12)", "pytest (>=4.0)", "pyt
|
|||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "310"
|
||||
version = "308"
|
||||
description = "Python for Window Extensions"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["package"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"},
|
||||
{file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"},
|
||||
{file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"},
|
||||
{file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"},
|
||||
{file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"},
|
||||
{file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"},
|
||||
{file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"},
|
||||
{file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"},
|
||||
{file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"},
|
||||
{file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"},
|
||||
{file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"},
|
||||
{file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"},
|
||||
{file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"},
|
||||
{file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"},
|
||||
{file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"},
|
||||
{file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"},
|
||||
{file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"},
|
||||
{file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"},
|
||||
{file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"},
|
||||
{file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"},
|
||||
{file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"},
|
||||
{file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"},
|
||||
{file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"},
|
||||
{file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"},
|
||||
{file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"},
|
||||
{file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"},
|
||||
{file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"},
|
||||
{file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"},
|
||||
{file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"},
|
||||
{file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"},
|
||||
{file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"},
|
||||
{file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"},
|
||||
{file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"},
|
||||
{file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1178,8 +1040,6 @@ version = "0.28"
|
|||
description = "PyXDG contains implementations of freedesktop.org standards in python."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\""
|
||||
files = [
|
||||
{file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"},
|
||||
{file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"},
|
||||
|
@ -1191,8 +1051,6 @@ version = "2.32.3"
|
|||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
|
@ -1210,54 +1068,50 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.1"
|
||||
version = "0.9.6"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "ruff-0.11.1-py3-none-linux_armv6l.whl", hash = "sha256:9c833671aaefcbe280aa54da387264402ffbb1e513ff3420c9c7265ea56d6c5c"},
|
||||
{file = "ruff-0.11.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5a57cd457764228c73066b832040728b02a3837c53c8a781a960b68129c4e0b"},
|
||||
{file = "ruff-0.11.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:da91da0d42e70cd8bda8e6687fab2afd28513a3cc9434539f4149610e63baf8f"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:429a2e533e3a0dba2ba7e0608a736e728150aa9b6d179245aa11a1339baa968b"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6bbcc2984a4d5cbc0f7b10409e74a00a043be45d813e5e81eb58e707455df7f1"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d9c283ebc88faa5bc23fa33f399b6d47a93f5980c92edcddf1f2127aa376b3"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1f2b03d504516d6b22065ce7fac2564dac15d79a6a776452dabfdd7673a45b07"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b95a9071f5ad8552af890bd814c6a04daf5b27297ac1054e3667016f3ab739"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28e2d89e7ba8a1525cdb50bc86c07aba35e7bbeef86dad93781b14ad94dc732c"},
|
||||
{file = "ruff-0.11.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e76be5a98dc6c29d85dfa72eb419e8d9276ee96ccf5c33f2b6828001907dcb17"},
|
||||
{file = "ruff-0.11.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:441f94c44fe250691c92382ef84f40acef290766fb3e819a9035e83eadd4dbbe"},
|
||||
{file = "ruff-0.11.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:62882a4cc7c0a48c2f34189bd4c7ba45f3d0efb990e02413eeb180aa042a39ca"},
|
||||
{file = "ruff-0.11.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:111dbad1706d8200a7138237b4766b45ba7ee45cc8299c02102f4327624f86a2"},
|
||||
{file = "ruff-0.11.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e2df41763d7a9fd438b6b7bde7b75eb3a92ef2f4682ed2d8e4b997b5f0c76ca9"},
|
||||
{file = "ruff-0.11.1-py3-none-win32.whl", hash = "sha256:e17b85919d461583aa7e0171bb4f419a6545b261ca080984db49b1f8dced1d4b"},
|
||||
{file = "ruff-0.11.1-py3-none-win_amd64.whl", hash = "sha256:caa872941b876f7ad73abc60144f9a7f6efb575e4f91c4fc1517f0339bcea01e"},
|
||||
{file = "ruff-0.11.1-py3-none-win_arm64.whl", hash = "sha256:7aa939fa57ef6770d18bd5cf0d6de77198dd762a559bd0d4a8763bdae4c8cc16"},
|
||||
{file = "ruff-0.11.1.tar.gz", hash = "sha256:f2e209a283c9fa423e268cad015ec4fb249178608f755fb67491ff175ecbffbf"},
|
||||
{file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"},
|
||||
{file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"},
|
||||
{file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"},
|
||||
{file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"},
|
||||
{file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"},
|
||||
{file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"},
|
||||
{file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"},
|
||||
{file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"},
|
||||
{file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "75.9.1"
|
||||
version = "75.8.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["package"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "setuptools-75.9.1-py3-none-any.whl", hash = "sha256:0a6f876d62f4d978ca1a11ab4daf728d1357731f978543ff18ecdbf9fd071f73"},
|
||||
{file = "setuptools-75.9.1.tar.gz", hash = "sha256:b6eca2c3070cdc82f71b4cb4bb2946bc0760a210d11362278cf1ff394e6ea32c"},
|
||||
{file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
|
||||
{file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
|
||||
core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "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) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.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) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "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) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
|
||||
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.7.2)", "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.14.*)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "shiboken6"
|
||||
|
@ -1265,8 +1119,6 @@ version = "6.8.2.1"
|
|||
description = "Python/C++ bindings helper module"
|
||||
optional = false
|
||||
python-versions = "<3.14,>=3.9"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "shiboken6-6.8.2.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:d3dedeb3732ecfc920c9f97da769c0022a1c3bda99346a9eba56fbf093deaa75"},
|
||||
{file = "shiboken6-6.8.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c83e90056f13d0872cc4d2b7bf60b6d6e3b1b172f1f91910c0ba5b641af01758"},
|
||||
|
@ -1280,8 +1132,6 @@ version = "1.3.1"
|
|||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||
|
@ -1293,8 +1143,6 @@ version = "0.1.1"
|
|||
description = "Strip ANSI escape sequences from a string"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4.0"
|
||||
groups = ["test"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "strip-ansi-0.1.1.tar.gz", hash = "sha256:5d60f239cc8a37fdd52b43c3e66e893d45ba0423115db59eca0d2eef83b07729"},
|
||||
{file = "strip_ansi-0.1.1-py3-none-any.whl", hash = "sha256:9f55280e1b0ba84dac49d4f18aa6b51b90ff766b22e4918ffc01cc87b394ecd1"},
|
||||
|
@ -1306,7 +1154,6 @@ version = "2.2.1"
|
|||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["lint", "package", "test"]
|
||||
files = [
|
||||
{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"},
|
||||
|
@ -1341,7 +1188,6 @@ files = [
|
|||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
markers = {lint = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.11\"", package = "sys_platform == \"win32\" and python_version < \"3.11\"", test = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_full_version <= \"3.11.0a6\""}
|
||||
|
||||
[[package]]
|
||||
name = "types-colorama"
|
||||
|
@ -1349,8 +1195,6 @@ version = "0.4.15.20240311"
|
|||
description = "Typing stubs for colorama"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
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"},
|
||||
|
@ -1362,8 +1206,6 @@ version = "0.21.0.20241128"
|
|||
description = "Typing stubs for docutils"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
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"},
|
||||
|
@ -1375,8 +1217,6 @@ version = "3.7.0.20241204"
|
|||
description = "Typing stubs for Markdown"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{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"},
|
||||
|
@ -1384,19 +1224,18 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "types-pygments"
|
||||
version = "2.19.0.20250305"
|
||||
version = "2.19.0.20250219"
|
||||
description = "Typing stubs for Pygments"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "types_pygments-2.19.0.20250305-py3-none-any.whl", hash = "sha256:ca88aae5ec426f9b107c0f7adc36dc096d2882d930a49f679eaf4b8b643db35d"},
|
||||
{file = "types_pygments-2.19.0.20250305.tar.gz", hash = "sha256:044c50e80ecd4128c00a7268f20355e16f5c55466d3d49dfda09be920af40b4b"},
|
||||
{file = "types_Pygments-2.19.0.20250219-py3-none-any.whl", hash = "sha256:5e3e1f660665b3a2ea946dda794b8d5b05772d789181704b523d646e8a7f4382"},
|
||||
{file = "types_pygments-2.19.0.20250219.tar.gz", hash = "sha256:a4a279338c96f3d4f2eb2c4d7c6c5593c88108b185bb5c664f943f781170cd14"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
types-docutils = "*"
|
||||
types-setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "types-pyside2"
|
||||
|
@ -1404,8 +1243,6 @@ version = "5.15.2.1.7"
|
|||
description = "The most accurate stubs for PySide2"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "types_pyside2-5.15.2.1.7-py2.py3-none-any.whl", hash = "sha256:a7bec4cb4657179415ca7ec7c70a45f9f9938664e22f385c85fd7cd724b07d4d"},
|
||||
{file = "types_pyside2-5.15.2.1.7.tar.gz", hash = "sha256:1d65072deb97481ad481b3414f94d02fd5da07f5e709c2d439ced14f79b2537c"},
|
||||
|
@ -1413,32 +1250,39 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20250306"
|
||||
version = "2.32.0.20241016"
|
||||
description = "Typing stubs for requests"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"},
|
||||
{file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"},
|
||||
{file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"},
|
||||
{file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
urllib3 = ">=2"
|
||||
|
||||
[[package]]
|
||||
name = "types-setuptools"
|
||||
version = "75.8.0.20250210"
|
||||
description = "Typing stubs for setuptools"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "types_setuptools-75.8.0.20250210-py3-none-any.whl", hash = "sha256:a217d7b4d59be04c29e23d142c959a0f85e71292fd3fc4313f016ca11f0b56dc"},
|
||||
{file = "types_setuptools-75.8.0.20250210.tar.gz", hash = "sha256:c1547361b2441f07c94e25dce8a068e18c611593ad4b6fdd727b1a8f5d1fda33"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev", "lint", "package"]
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
markers = {dev = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.13\"", lint = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\"", package = "sys_platform == \"win32\" and python_version < \"3.10\""}
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
|
@ -1446,15 +1290,13 @@ version = "2.3.0"
|
|||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "lint"]
|
||||
markers = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
files = [
|
||||
{file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
|
||||
{file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
@ -1465,22 +1307,20 @@ version = "3.21.0"
|
|||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main", "package"]
|
||||
files = [
|
||||
{file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
|
||||
{file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
|
||||
]
|
||||
markers = {main = "(platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.10\"", package = "platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or sys_platform != \"linux\" or platform_machine != \"x86_64\" and platform_machine != \"i686\" and platform_machine != \"aarch64\" and platform_machine != \"armv7l\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""}
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
||||
cover = ["pytest-cov"]
|
||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
enabler = ["pytest-enabler (>=2.2)"]
|
||||
test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
|
||||
type = ["pytest-mypy"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.14"
|
||||
content-hash = "7f04a83bfbf6d8ade9275d9f4d8f38fc51cd0522db79666730f78a050895cc4a"
|
||||
content-hash = "bd66b4a55c137f803902b235627ca4399c30d5428be8be925ca8d2a394c5dc80"
|
||||
|
|
|
@ -57,10 +57,9 @@ pytest-cov = "^5.0.0"
|
|||
strip-ansi = "*"
|
||||
pytest-subprocess = "^1.5.2"
|
||||
pytest-rerunfailures = "^14.0"
|
||||
numpy = "2.0" # bump when we remove python 3.9 support
|
||||
|
||||
[tool.poetry.group.debian.dependencies]
|
||||
pymupdf = "^1.24.11"
|
||||
pymupdf = "1.24.11" # Last version to support python 3.8 (needed for Ubuntu Focal support)
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
httpx = "^0.27.2"
|
||||
|
|
|
@ -122,7 +122,7 @@ test_docs_compressed_dir = Path(__file__).parent.joinpath(SAMPLE_COMPRESSED_DIRE
|
|||
|
||||
test_docs = [
|
||||
p
|
||||
for p in test_docs_dir.glob("*")
|
||||
for p in test_docs_dir.rglob("*")
|
||||
if p.is_file()
|
||||
and not (p.name.endswith(SAFE_EXTENSION) or p.name.startswith("sample_bad"))
|
||||
]
|
||||
|
@ -160,31 +160,3 @@ def for_each_external_doc(glob_pattern: str = "*") -> Callable:
|
|||
|
||||
class TestBase:
|
||||
sample_doc = str(test_docs_dir.joinpath(BASIC_SAMPLE_PDF))
|
||||
|
||||
|
||||
def pytest_configure(config: pytest.Config) -> None:
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"reference_generator: Used to mark the test cases that regenerate reference documents",
|
||||
)
|
||||
|
||||
|
||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
parser.addoption(
|
||||
"--generate-reference-pdfs",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Regenerate reference PDFs",
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(
|
||||
config: pytest.Config, items: List[pytest.Item]
|
||||
) -> None:
|
||||
if not config.getoption("--generate-reference-pdfs"):
|
||||
skip_generator = pytest.mark.skip(
|
||||
reason="Only run when --generate-reference-pdfs is provided"
|
||||
)
|
||||
for item in items:
|
||||
if "reference_generator" in item.keywords:
|
||||
item.add_marker(skip_generator)
|
||||
|
|
|
@ -8,7 +8,6 @@ from pytest_subprocess import FakeProcess
|
|||
from dangerzone import container_utils, errors
|
||||
from dangerzone.isolation_provider.container import Container
|
||||
from dangerzone.isolation_provider.qubes import is_qubes_native_conversion
|
||||
from dangerzone.util import get_resource_path
|
||||
|
||||
from .base import IsolationProviderTermination, IsolationProviderTest
|
||||
|
||||
|
@ -48,7 +47,7 @@ class TestContainer(IsolationProviderTest):
|
|||
provider.is_available()
|
||||
|
||||
def test_install_raise_if_image_cant_be_installed(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""When an image installation fails, an exception should be raised"""
|
||||
|
||||
|
@ -69,13 +68,11 @@ class TestContainer(IsolationProviderTest):
|
|||
occurrences=2,
|
||||
)
|
||||
|
||||
# Make podman load fail
|
||||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
|
||||
fp.register_subprocess(
|
||||
[
|
||||
container_utils.get_runtime(),
|
||||
"load",
|
||||
"-i",
|
||||
get_resource_path("container.tar"),
|
||||
],
|
||||
[container_utils.get_runtime(), "load"],
|
||||
returncode=-1,
|
||||
)
|
||||
|
||||
|
@ -83,13 +80,9 @@ class TestContainer(IsolationProviderTest):
|
|||
provider.install()
|
||||
|
||||
def test_install_raises_if_still_not_installed(
|
||||
self, provider: Container, fp: FakeProcess
|
||||
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
|
||||
) -> None:
|
||||
"""When an image keep being not installed, it should return False"""
|
||||
fp.register_subprocess(
|
||||
["podman", "version", "-f", "{{.Client.Version}}"],
|
||||
stdout="4.0.0",
|
||||
)
|
||||
|
||||
fp.register_subprocess(
|
||||
[container_utils.get_runtime(), "image", "ls"],
|
||||
|
@ -108,13 +101,10 @@ class TestContainer(IsolationProviderTest):
|
|||
occurrences=2,
|
||||
)
|
||||
|
||||
# Patch gzip.open and podman load so that it works
|
||||
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
|
||||
fp.register_subprocess(
|
||||
[
|
||||
container_utils.get_runtime(),
|
||||
"load",
|
||||
"-i",
|
||||
get_resource_path("container.tar"),
|
||||
],
|
||||
[container_utils.get_runtime(), "load"],
|
||||
)
|
||||
with pytest.raises(errors.ImageNotPresentException):
|
||||
provider.install()
|
||||
|
@ -201,7 +191,7 @@ class TestContainer(IsolationProviderTest):
|
|||
reason="Linux specific",
|
||||
)
|
||||
def test_linux_skips_desktop_version_check_returns_true(
|
||||
self, provider: Container
|
||||
self, mocker: MockerFixture, provider: Container
|
||||
) -> None:
|
||||
assert (True, "") == provider.check_docker_desktop_version()
|
||||
|
||||
|
|
|
@ -7,13 +7,10 @@ import platform
|
|||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Optional, Sequence
|
||||
|
||||
import fitz
|
||||
import numpy as np
|
||||
import pytest
|
||||
from click.testing import CliRunner, Result
|
||||
from pytest_mock import MockerFixture
|
||||
|
@ -193,68 +190,11 @@ class TestCliConversion(TestCliBasic):
|
|||
result = self.run_cli([sample_pdf, "--ocr-lang", "piglatin"])
|
||||
result.assert_failure()
|
||||
|
||||
@pytest.mark.reference_generator
|
||||
@for_each_doc
|
||||
def test_regenerate_reference(self, doc: Path) -> None:
|
||||
reference = (doc.parent / "reference" / doc.stem).with_suffix(".pdf")
|
||||
|
||||
result = self.run_cli([str(doc), "--output-filename", str(reference)])
|
||||
def test_formats(self, doc: Path) -> None:
|
||||
result = self.run_cli(str(doc))
|
||||
result.assert_success()
|
||||
|
||||
@for_each_doc
|
||||
def test_formats(self, doc: Path, tmp_path_factory: pytest.TempPathFactory) -> None:
|
||||
reference = (doc.parent / "reference" / doc.stem).with_suffix(".pdf")
|
||||
destination = tmp_path_factory.mktemp(doc.stem).with_suffix(".pdf")
|
||||
|
||||
result = self.run_cli([str(doc), "--output-filename", str(destination)])
|
||||
result.assert_success()
|
||||
|
||||
# Do not check against reference versions when using a dummy isolation provider
|
||||
if os.environ.get("DUMMY_CONVERSION", False):
|
||||
return
|
||||
|
||||
converted = fitz.open(destination)
|
||||
ref = fitz.open(reference)
|
||||
errors = []
|
||||
if len(converted) != len(ref):
|
||||
errors.append("different number of pages")
|
||||
|
||||
diffs = doc.parent / "diffs"
|
||||
diffs.mkdir(parents=True, exist_ok=True)
|
||||
for page, ref_page in zip(converted, ref):
|
||||
curr_pixmap = page.get_pixmap(dpi=150)
|
||||
ref_pixmap = ref_page.get_pixmap(dpi=150)
|
||||
if curr_pixmap.tobytes() != ref_pixmap.tobytes():
|
||||
errors.append(f"page {page.number} differs")
|
||||
|
||||
t0 = time.perf_counter()
|
||||
|
||||
arr_ref = np.frombuffer(ref_pixmap.samples, dtype=np.uint8).reshape(
|
||||
ref_pixmap.height, ref_pixmap.width, ref_pixmap.n
|
||||
)
|
||||
arr_curr = np.frombuffer(curr_pixmap.samples, dtype=np.uint8).reshape(
|
||||
curr_pixmap.height, curr_pixmap.width, curr_pixmap.n
|
||||
)
|
||||
|
||||
# Find differences (any channel differs)
|
||||
diff = (arr_ref != arr_curr).any(axis=2)
|
||||
|
||||
# Get coordinates of differences
|
||||
diff_coords = np.where(diff)
|
||||
# Mark differences in red
|
||||
for y, x in zip(diff_coords[0], diff_coords[1]):
|
||||
# Note: PyMuPDF's set_pixel takes (x, y) not (y, x)
|
||||
ref_pixmap.set_pixel(int(x), int(y), (255, 0, 0)) # Red
|
||||
|
||||
t1 = time.perf_counter()
|
||||
print(f"diff took {t1 - t0} seconds")
|
||||
ref_pixmap.save(diffs / f"{destination.stem}_{page.number}.jpeg")
|
||||
|
||||
if len(errors) > 0:
|
||||
raise AssertionError(
|
||||
f"The resulting document differs from the reference. See {str(diffs)} for a visual diff."
|
||||
)
|
||||
|
||||
def test_output_filename(self, sample_pdf: str) -> None:
|
||||
temp_dir = tempfile.mkdtemp(prefix="dangerzone-")
|
||||
output_filename = str(Path(temp_dir) / "safe.pdf")
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue