mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-05 05:01:49 +02:00
Compare commits
30 commits
021e56d166
...
3a6b99cb24
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3a6b99cb24 | ||
![]() |
d55fa8e87c | ||
![]() |
a33e870915 | ||
![]() |
ce85bde109 | ||
![]() |
95239b5f88 | ||
![]() |
32626b4b99 | ||
![]() |
e7ae5f3cb2 | ||
![]() |
58ecccfba2 | ||
![]() |
10bc6aa8f1 | ||
![]() |
7f5793fada | ||
![]() |
90a2843836 | ||
![]() |
84b8c5106c | ||
![]() |
ba84c0fb68 | ||
![]() |
4d13333187 | ||
![]() |
38890259eb | ||
![]() |
a2c9e9deed | ||
![]() |
3c1e82d4f1 | ||
![]() |
17f802915b | ||
![]() |
9104af81c3 | ||
![]() |
c62efc7cdd | ||
![]() |
435ebf0072 | ||
![]() |
6049a627cb | ||
![]() |
ba20f5a4d0 | ||
![]() |
b069e0ee10 | ||
![]() |
d722800a4b | ||
![]() |
4cfc633cdb | ||
![]() |
944d58dd8d | ||
![]() |
f3806b96af | ||
![]() |
c4bb7c28c8 | ||
![]() |
630083bdea |
14 changed files with 604 additions and 182 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -1,6 +1,10 @@
|
||||||
name: Build dev environments
|
name: Build dev environments
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- "test/**"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *" # Run every day at 00:00 UTC.
|
- cron: "0 0 * * *" # Run every day at 00:00 UTC.
|
||||||
|
|
||||||
|
|
2
.github/workflows/check_push.yml
vendored
2
.github/workflows/check_push.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Check branch conformity
|
name: Check branch conformity
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prevent-fixup-commits:
|
prevent-fixup-commits:
|
||||||
|
|
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
|
@ -1,8 +1,10 @@
|
||||||
name: Tests
|
name: Tests
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- "test/**"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "2 0 * * *" # Run every day at 02:00 UTC.
|
- cron: "2 0 * * *" # Run every day at 02:00 UTC.
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -91,7 +93,8 @@ jobs:
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: download-tessdata
|
needs:
|
||||||
|
- download-tessdata
|
||||||
env:
|
env:
|
||||||
DUMMY_CONVERSION: 1
|
DUMMY_CONVERSION: 1
|
||||||
steps:
|
steps:
|
||||||
|
@ -110,10 +113,14 @@ jobs:
|
||||||
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
|
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
|
||||||
- name: Run CLI tests
|
- name: Run CLI tests
|
||||||
run: poetry run make test
|
run: poetry run make test
|
||||||
# Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829
|
- name: Set up .NET CLI environment
|
||||||
- name: Set path for candle and light
|
uses: actions/setup-dotnet@v4
|
||||||
run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH
|
with:
|
||||||
shell: bash
|
dotnet-version: "8.x"
|
||||||
|
- name: Install WiX Toolset
|
||||||
|
run: dotnet tool install --global wix
|
||||||
|
- name: Add WiX UI extension
|
||||||
|
run: wix extension add --global WixToolset.UI.wixext
|
||||||
- name: Build the MSI installer
|
- name: Build the MSI installer
|
||||||
# NOTE: This also builds the .exe internally.
|
# NOTE: This also builds the .exe internally.
|
||||||
run: poetry run .\install\windows\build-app.bat
|
run: poetry run .\install\windows\build-app.bat
|
||||||
|
@ -121,7 +128,8 @@ jobs:
|
||||||
macOS:
|
macOS:
|
||||||
name: "macOS (${{ matrix.arch }})"
|
name: "macOS (${{ matrix.arch }})"
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
needs: download-tessdata
|
needs:
|
||||||
|
- download-tessdata
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
@ -149,9 +157,10 @@ jobs:
|
||||||
run: poetry run make test
|
run: poetry run make test
|
||||||
|
|
||||||
build-deb:
|
build-deb:
|
||||||
|
needs:
|
||||||
|
- build-container-image
|
||||||
name: "build-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
name: "build-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-container-image
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
@ -219,7 +228,8 @@ jobs:
|
||||||
install-deb:
|
install-deb:
|
||||||
name: "install-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
name: "install-deb (${{ matrix.distro }} ${{ matrix.version }})"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-deb
|
needs:
|
||||||
|
- build-deb
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
@ -273,7 +283,8 @@ jobs:
|
||||||
build-install-rpm:
|
build-install-rpm:
|
||||||
name: "build-install-rpm (${{ matrix.distro }} ${{matrix.version}})"
|
name: "build-install-rpm (${{ matrix.distro }} ${{matrix.version}})"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build-container-image
|
needs:
|
||||||
|
- build-container-image
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
distro: ["fedora"]
|
distro: ["fedora"]
|
||||||
|
|
3
.github/workflows/scan.yml
vendored
3
.github/workflows/scan.yml
vendored
|
@ -1,8 +1,9 @@
|
||||||
name: Scan latest app and container
|
name: Scan latest app and container
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * *' # Run every day at 00:00 UTC.
|
- cron: '0 0 * * *' # Run every day at 00:00 UTC.
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
13
.github/workflows/scan_released.yml
vendored
13
.github/workflows/scan_released.yml
vendored
|
@ -6,14 +6,21 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security-scan-container:
|
security-scan-container:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runs-on: ubuntu-latest
|
||||||
|
arch: i686
|
||||||
|
- runs-on: macos-latest
|
||||||
|
arch: arm64
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Download container image for the latest release and load it
|
- name: Download container image for the latest release and load it
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(curl https://api.github.com/repos/freedomofpress/dangerzone/releases/latest | jq -r '.tag_name')
|
VERSION=$(curl https://api.github.com/repos/freedomofpress/dangerzone/releases/latest | jq -r '.tag_name')
|
||||||
CONTAINER_FILENAME=container-${VERSION:1}-i686.tar.gz
|
CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar.gz
|
||||||
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
|
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
|
||||||
docker load -i ${CONTAINER_FILENAME}
|
docker load -i ${CONTAINER_FILENAME}
|
||||||
# NOTE: Scan first without failing, else we won't be able to read the scan
|
# NOTE: Scan first without failing, else we won't be able to read the scan
|
||||||
|
@ -30,7 +37,7 @@ jobs:
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ steps.scan_container.outputs.sarif }}
|
sarif_file: ${{ steps.scan_container.outputs.sarif }}
|
||||||
category: container
|
category: container-${{ matrix.arch }}
|
||||||
- name: Inspect container scan report
|
- name: Inspect container scan report
|
||||||
run: cat ${{ steps.scan_container.outputs.sarif }}
|
run: cat ${{ steps.scan_container.outputs.sarif }}
|
||||||
- name: Scan container image
|
- name: Scan container image
|
||||||
|
|
21
BUILD.md
21
BUILD.md
|
@ -474,11 +474,24 @@ poetry shell
|
||||||
.\dev_scripts\dangerzone.bat
|
.\dev_scripts\dangerzone.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
### If you want to build the installer
|
### If you want to build the Windows installer
|
||||||
|
|
||||||
* Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`.
|
Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) version 6 or later. Then, open a terminal and install the latest version of [WiX Toolset .NET tool](https://wixtoolset.org/) **v5** with:
|
||||||
* Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix314.exe`.
|
|
||||||
* Add `C:\Program Files (x86)\WiX Toolset v3.14\bin` to the path ([instructions](https://web.archive.org/web/20230221104142/https://windowsloop.com/how-to-add-to-windows-path/)).
|
```sh
|
||||||
|
dotnet tool install --global wix --version 5.*
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the WiX UI extension. You may need to open a new terminal in order to use the newly installed `wix` .NET tool:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wix extension add --global WixToolset.UI.wixext/5.x.y
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> To avoid compatibility issues, ensure the WiX UI extension version matches the version of the WiX Toolset.
|
||||||
|
>
|
||||||
|
> Run `wix --version` to check the version of WiX Toolset you have installed and replace `5.x.y` with the full version number without the Git revision.
|
||||||
|
|
||||||
### If you want to sign binaries with Authenticode
|
### If you want to sign binaries with Authenticode
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
|
||||||
|
|
||||||
## [Unreleased](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...HEAD)
|
## [Unreleased](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...HEAD)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Disable gVisor's DirectFS feature ([#226](https://github.com/freedomofpress/dangerzone/issues/226)).
|
||||||
|
Thanks [EtiennePerot](https://github.com/EtiennePerot) for the contribution.
|
||||||
|
|
||||||
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
|
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -74,9 +74,7 @@ FROM alpine:latest
|
||||||
RUN apk --no-cache -U upgrade && \
|
RUN apk --no-cache -U upgrade && \
|
||||||
apk --no-cache add python3
|
apk --no-cache add python3
|
||||||
|
|
||||||
# Temporarily pin gVisor to the latest working version (release-20240826.0).
|
RUN GVISOR_URL="https://storage.googleapis.com/gvisor/releases/release/latest/$(uname -m)"; \
|
||||||
# See: https://github.com/freedomofpress/dangerzone/issues/928
|
|
||||||
RUN GVISOR_URL="https://storage.googleapis.com/gvisor/releases/release/20240826/$(uname -m)"; \
|
|
||||||
wget "${GVISOR_URL}/runsc" "${GVISOR_URL}/runsc.sha512" && \
|
wget "${GVISOR_URL}/runsc" "${GVISOR_URL}/runsc.sha512" && \
|
||||||
sha512sum -c runsc.sha512 && \
|
sha512sum -c runsc.sha512 && \
|
||||||
rm -f runsc.sha512 && \
|
rm -f runsc.sha512 && \
|
||||||
|
|
|
@ -142,6 +142,9 @@ runsc_argv = [
|
||||||
"--rootless=true",
|
"--rootless=true",
|
||||||
"--network=none",
|
"--network=none",
|
||||||
"--root=/home/dangerzone/.containers",
|
"--root=/home/dangerzone/.containers",
|
||||||
|
# Disable DirectFS for to make the seccomp filter even stricter,
|
||||||
|
# at some performance cost.
|
||||||
|
"--directfs=false",
|
||||||
]
|
]
|
||||||
if os.environ.get("RUNSC_DEBUG"):
|
if os.environ.get("RUNSC_DEBUG"):
|
||||||
runsc_argv += ["--debug=true", "--alsologtostderr=true"]
|
runsc_argv += ["--debug=true", "--alsologtostderr=true"]
|
||||||
|
|
254
dev_scripts/generate-release-notes.py
Executable file
254
dev_scripts/generate-release-notes.py
Executable file
|
@ -0,0 +1,254 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
REPOSITORY = "https://github.com/freedomofpress/dangerzone/"
|
||||||
|
TEMPLATE = "- {title} ([#{number}]({url}))"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version(version: str) -> Tuple[int, int]:
|
||||||
|
"""Extract major.minor from version string, ignoring patch"""
|
||||||
|
match = re.match(r"v?(\d+)\.(\d+)", version)
|
||||||
|
if not match:
|
||||||
|
raise ValueError(f"Invalid version format: {version}")
|
||||||
|
return (int(match.group(1)), int(match.group(2)))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_last_minor_release(
|
||||||
|
client: httpx.AsyncClient, owner: str, repo: str
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Get the latest minor release date (ignoring patches)"""
|
||||||
|
response = await client.get(f"https://api.github.com/repos/{owner}/{repo}/releases")
|
||||||
|
response.raise_for_status()
|
||||||
|
releases = response.json()
|
||||||
|
|
||||||
|
if not releases:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the latest minor version by comparing major.minor numbers
|
||||||
|
current_version = parse_version(releases[0]["tag_name"])
|
||||||
|
latest_date = None
|
||||||
|
|
||||||
|
for release in releases:
|
||||||
|
try:
|
||||||
|
version = parse_version(release["tag_name"])
|
||||||
|
if version < current_version:
|
||||||
|
latest_date = release["published_at"]
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return latest_date
|
||||||
|
|
||||||
|
|
||||||
|
async def get_issue_details(
|
||||||
|
client: httpx.AsyncClient, owner: str, repo: str, issue_number: int
|
||||||
|
) -> Optional[dict]:
|
||||||
|
"""Get issue title and number if it exists"""
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"
|
||||||
|
)
|
||||||
|
if response.is_success:
|
||||||
|
data = response.json()
|
||||||
|
return {
|
||||||
|
"title": data["title"],
|
||||||
|
"number": data["number"],
|
||||||
|
"url": data["html_url"],
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_issue_number(pr_body: Optional[str]) -> Optional[int]:
|
||||||
|
"""Extract issue number from PR body looking for common formats like 'Fixes #123' or 'Closes #123'"""
|
||||||
|
if not pr_body:
|
||||||
|
return None
|
||||||
|
|
||||||
|
patterns = [
|
||||||
|
r"(?:closes|fixes|resolves)\s*#(\d+)",
|
||||||
|
r"(?:close|fix|resolve)\s*#(\d+)",
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
match = re.search(pattern, pr_body.lower())
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def verify_commit_in_master(
|
||||||
|
client: httpx.AsyncClient, owner: str, repo: str, commit_id: str
|
||||||
|
) -> bool:
|
||||||
|
"""Verify if a commit exists in master"""
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/commits/{commit_id}"
|
||||||
|
)
|
||||||
|
return response.is_success and response.json().get("commit") is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def process_issue_events(
|
||||||
|
client: httpx.AsyncClient, owner: str, repo: str, issue: Dict
|
||||||
|
) -> Optional[Dict]:
|
||||||
|
"""Process events for a single issue"""
|
||||||
|
events_response = await client.get(f"{issue['url']}/events")
|
||||||
|
if not events_response.is_success:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for event in events_response.json():
|
||||||
|
if event["event"] == "closed" and event.get("commit_id"):
|
||||||
|
if await verify_commit_in_master(client, owner, repo, event["commit_id"]):
|
||||||
|
return {
|
||||||
|
"title": issue["title"],
|
||||||
|
"number": issue["number"],
|
||||||
|
"url": issue["html_url"],
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_closed_issues(
|
||||||
|
client: httpx.AsyncClient, owner: str, repo: str, since: str
|
||||||
|
) -> List[Dict]:
|
||||||
|
"""Get issues closed by commits to master since the given date"""
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/issues",
|
||||||
|
params={
|
||||||
|
"state": "closed",
|
||||||
|
"sort": "updated",
|
||||||
|
"direction": "desc",
|
||||||
|
"since": since,
|
||||||
|
"per_page": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
tasks = []
|
||||||
|
since_date = datetime.strptime(since, "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
for issue in response.json():
|
||||||
|
if "pull_request" in issue:
|
||||||
|
continue
|
||||||
|
|
||||||
|
closed_at = datetime.strptime(issue["closed_at"], "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
if closed_at <= since_date:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tasks.append(process_issue_events(client, owner, repo, issue))
|
||||||
|
|
||||||
|
results = await asyncio.gather(*tasks)
|
||||||
|
return [r for r in results if r is not None]
|
||||||
|
|
||||||
|
|
||||||
|
async def process_pull_request(
|
||||||
|
client: httpx.AsyncClient,
|
||||||
|
owner: str,
|
||||||
|
repo: str,
|
||||||
|
pr: Dict,
|
||||||
|
closed_issues: List[Dict],
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Process a single pull request"""
|
||||||
|
issue_number = extract_issue_number(pr.get("body"))
|
||||||
|
if issue_number:
|
||||||
|
issue = await get_issue_details(client, owner, repo, issue_number)
|
||||||
|
if issue:
|
||||||
|
if not any(i["number"] == issue["number"] for i in closed_issues):
|
||||||
|
return TEMPLATE.format(**issue)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return TEMPLATE.format(title=pr["title"], number=pr["number"], url=pr["html_url"])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_changes_since_last_release(
|
||||||
|
owner: str, repo: str, token: Optional[str] = None
|
||||||
|
) -> List[str]:
|
||||||
|
headers = {
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
}
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"token {token}"
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Warning: No token provided. API rate limiting may occur.", file=sys.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
async with httpx.AsyncClient(headers=headers, timeout=30.0) as client:
|
||||||
|
# Get the date of last minor release
|
||||||
|
since = await get_last_minor_release(client, owner, repo)
|
||||||
|
if not since:
|
||||||
|
return []
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
# Get issues closed by commits to master
|
||||||
|
closed_issues = await get_closed_issues(client, owner, repo, since)
|
||||||
|
changes.extend([TEMPLATE.format(**issue) for issue in closed_issues])
|
||||||
|
|
||||||
|
# Get merged PRs
|
||||||
|
response = await client.get(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/pulls",
|
||||||
|
params={
|
||||||
|
"state": "closed",
|
||||||
|
"sort": "updated",
|
||||||
|
"direction": "desc",
|
||||||
|
"per_page": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Process PRs in parallel
|
||||||
|
pr_tasks = []
|
||||||
|
for pr in response.json():
|
||||||
|
if not pr["merged_at"]:
|
||||||
|
continue
|
||||||
|
if since and pr["merged_at"] <= since:
|
||||||
|
break
|
||||||
|
|
||||||
|
pr_tasks.append(
|
||||||
|
process_pull_request(client, owner, repo, pr, closed_issues)
|
||||||
|
)
|
||||||
|
|
||||||
|
pr_results = await asyncio.gather(*pr_tasks)
|
||||||
|
changes.extend([r for r in pr_results if r is not None])
|
||||||
|
|
||||||
|
return changes
|
||||||
|
|
||||||
|
|
||||||
|
async def main_async():
|
||||||
|
parser = argparse.ArgumentParser(description="Generate release notes from GitHub")
|
||||||
|
parser.add_argument("--token", "-t", help="the file path to the GitHub API token")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
token = None
|
||||||
|
if args.token:
|
||||||
|
with open(args.token) as f:
|
||||||
|
token = f.read().strip()
|
||||||
|
try:
|
||||||
|
url_path = REPOSITORY.rstrip("/").split("github.com/")[1]
|
||||||
|
owner, repo = url_path.split("/")[-2:]
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
print("Error: Invalid GitHub URL", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
notes = await get_changes_since_last_release(owner, repo, token)
|
||||||
|
print("\n".join(notes))
|
||||||
|
except httpx.HTTPError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
asyncio.run(main_async())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -17,22 +17,23 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd
|
||||||
REM verify the signature of dangerzone-cli.exe
|
REM verify the signature of dangerzone-cli.exe
|
||||||
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
|
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
|
||||||
|
|
||||||
REM build the wix file
|
REM build the wxs file
|
||||||
python install\windows\build-wxs.py > build\Dangerzone.wxs
|
python install\windows\build-wxs.py
|
||||||
|
|
||||||
REM build the msi package
|
REM build the msi package
|
||||||
cd build
|
cd build
|
||||||
candle.exe Dangerzone.wxs
|
wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi
|
||||||
light.exe -ext WixUIExtension Dangerzone.wixobj
|
|
||||||
|
REM validate Dangerzone.msi
|
||||||
|
wix msi validate Dangerzone.msi
|
||||||
|
|
||||||
REM code sign Dangerzone.msi
|
REM code sign Dangerzone.msi
|
||||||
insignia.exe -im Dangerzone.msi
|
|
||||||
signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi
|
signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi
|
||||||
|
|
||||||
REM verify the signature of Dangerzone.msi
|
REM verify the signature of Dangerzone.msi
|
||||||
signtool.exe verify /pa Dangerzone.msi
|
signtool.exe verify /pa Dangerzone.msi
|
||||||
|
|
||||||
REM moving Dangerzone.msi to dist
|
REM move Dangerzone.msi to dist
|
||||||
cd ..
|
cd ..
|
||||||
mkdir dist
|
mkdir dist
|
||||||
move build\Dangerzone.msi dist
|
move build\Dangerzone.msi dist
|
||||||
|
|
|
@ -4,114 +4,75 @@ import uuid
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
def build_data(dirname, dir_prefix, id_, name):
|
def build_data(base_path, path_prefix, dir_id, dir_name):
|
||||||
data = {
|
data = {
|
||||||
"id": id_,
|
"directory_name": dir_name,
|
||||||
"name": name,
|
"directory_id": dir_id,
|
||||||
"files": [],
|
"files": [],
|
||||||
"dirs": [],
|
"dirs": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
for basename in os.listdir(dirname):
|
if dir_id == "INSTALLFOLDER":
|
||||||
filename = os.path.join(dirname, basename)
|
data["component_id"] = "ApplicationFiles"
|
||||||
if os.path.isfile(filename):
|
else:
|
||||||
data["files"].append(os.path.join(dir_prefix, basename))
|
data["component_id"] = "Component" + dir_id
|
||||||
elif os.path.isdir(filename):
|
data["component_guid"] = str(uuid.uuid4()).upper()
|
||||||
if id_ == "INSTALLDIR":
|
|
||||||
id_prefix = "Folder"
|
for entry in os.listdir(base_path):
|
||||||
|
entry_path = os.path.join(base_path, entry)
|
||||||
|
if os.path.isfile(entry_path):
|
||||||
|
data["files"].append(os.path.join(path_prefix, entry))
|
||||||
|
elif os.path.isdir(entry_path):
|
||||||
|
if dir_id == "INSTALLFOLDER":
|
||||||
|
next_dir_prefix = "Folder"
|
||||||
else:
|
else:
|
||||||
id_prefix = id_
|
next_dir_prefix = dir_id
|
||||||
|
|
||||||
# Skip lib/PySide6/examples folder due to ilegal file names
|
# Skip lib/PySide6/examples folder due to ilegal file names
|
||||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname:
|
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
|
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
|
||||||
# XXX Since we're not using Qml it should be no problem
|
# XXX Since we're not using Qml it should be no problem
|
||||||
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname:
|
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
|
next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_")
|
||||||
data["dirs"].append(
|
subdata = build_data(
|
||||||
build_data(
|
os.path.join(base_path, entry),
|
||||||
os.path.join(dirname, basename),
|
os.path.join(path_prefix, entry),
|
||||||
os.path.join(dir_prefix, basename),
|
next_dir_id,
|
||||||
id_value,
|
entry,
|
||||||
basename,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(data["files"]) > 0:
|
# Add the subdirectory only if it contains files or subdirectories
|
||||||
if id_ == "INSTALLDIR":
|
if subdata["files"] or subdata["dirs"]:
|
||||||
data["component_id"] = "ApplicationFiles"
|
data["dirs"].append(subdata)
|
||||||
else:
|
|
||||||
data["component_id"] = "FolderComponent" + id_[len("Folder") :]
|
|
||||||
data["component_guid"] = str(uuid.uuid4())
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def build_dir_xml(root, data):
|
def build_directory_xml(root, data):
|
||||||
attrs = {}
|
attrs = {}
|
||||||
if "id" in data:
|
attrs["Id"] = data["directory_id"]
|
||||||
attrs["Id"] = data["id"]
|
attrs["Name"] = data["directory_name"]
|
||||||
if "name" in data:
|
directory_el = ET.SubElement(root, "Directory", attrs)
|
||||||
attrs["Name"] = data["name"]
|
|
||||||
el = ET.SubElement(root, "Directory", attrs)
|
|
||||||
for subdata in data["dirs"]:
|
for subdata in data["dirs"]:
|
||||||
build_dir_xml(el, subdata)
|
build_directory_xml(directory_el, subdata)
|
||||||
|
|
||||||
# If this is the ProgramMenuFolder, add the menu component
|
|
||||||
if "id" in data and data["id"] == "ProgramMenuFolder":
|
|
||||||
component_el = ET.SubElement(
|
|
||||||
el,
|
|
||||||
"Component",
|
|
||||||
Id="ApplicationShortcuts",
|
|
||||||
Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc",
|
|
||||||
)
|
|
||||||
ET.SubElement(
|
|
||||||
component_el,
|
|
||||||
"Shortcut",
|
|
||||||
Id="ApplicationShortcut1",
|
|
||||||
Name="Dangerzone",
|
|
||||||
Description="Dangerzone",
|
|
||||||
Target="[INSTALLDIR]dangerzone.exe",
|
|
||||||
WorkingDirectory="INSTALLDIR",
|
|
||||||
)
|
|
||||||
ET.SubElement(
|
|
||||||
component_el,
|
|
||||||
"RegistryValue",
|
|
||||||
Root="HKCU",
|
|
||||||
Key="Software\Freedom of the Press Foundation\Dangerzone",
|
|
||||||
Name="installed",
|
|
||||||
Type="integer",
|
|
||||||
Value="1",
|
|
||||||
KeyPath="yes",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_components_xml(root, data):
|
def build_components_xml(root, data):
|
||||||
component_ids = []
|
component_el = ET.SubElement(
|
||||||
if "component_id" in data:
|
root,
|
||||||
component_ids.append(data["component_id"])
|
"Component",
|
||||||
|
Id=data["component_id"],
|
||||||
|
Guid=data["component_guid"],
|
||||||
|
Directory=data["directory_id"],
|
||||||
|
)
|
||||||
|
for filename in data["files"]:
|
||||||
|
ET.SubElement(component_el, "File", Source=filename)
|
||||||
for subdata in data["dirs"]:
|
for subdata in data["dirs"]:
|
||||||
if "component_guid" in subdata:
|
build_components_xml(root, subdata)
|
||||||
dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"])
|
|
||||||
component_el = ET.SubElement(
|
|
||||||
dir_ref_el,
|
|
||||||
"Component",
|
|
||||||
Id=subdata["component_id"],
|
|
||||||
Guid=subdata["component_guid"],
|
|
||||||
)
|
|
||||||
for filename in subdata["files"]:
|
|
||||||
file_el = ET.SubElement(
|
|
||||||
component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex
|
|
||||||
)
|
|
||||||
|
|
||||||
component_ids += build_components_xml(root, subdata)
|
|
||||||
|
|
||||||
return component_ids
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -125,120 +86,191 @@ def main():
|
||||||
# -rc markers.
|
# -rc markers.
|
||||||
version = f.read().strip().split("-")[0]
|
version = f.read().strip().split("-")[0]
|
||||||
|
|
||||||
dist_dir = os.path.join(
|
build_dir = os.path.join(
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
||||||
"build",
|
"build",
|
||||||
"exe.win-amd64-3.12",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cx_freeze_dir = "exe.win-amd64-3.12"
|
||||||
|
|
||||||
|
dist_dir = os.path.join(build_dir, cx_freeze_dir)
|
||||||
|
|
||||||
if not os.path.exists(dist_dir):
|
if not os.path.exists(dist_dir):
|
||||||
print("You must build the dangerzone binary before running this")
|
print("You must build the dangerzone binary before running this")
|
||||||
return
|
return
|
||||||
|
|
||||||
data = {
|
# Prepare data for WiX file harvesting from the output of cx_Freeze
|
||||||
"id": "TARGETDIR",
|
data = build_data(
|
||||||
"name": "SourceDir",
|
dist_dir,
|
||||||
"dirs": [
|
cx_freeze_dir,
|
||||||
{
|
"INSTALLFOLDER",
|
||||||
"id": "ProgramFilesFolder",
|
"Dangerzone",
|
||||||
"dirs": [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ProgramMenuFolder",
|
|
||||||
"dirs": [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
data["dirs"][0]["dirs"].append(
|
|
||||||
build_data(
|
|
||||||
dist_dir,
|
|
||||||
"exe.win-amd64-3.12",
|
|
||||||
"INSTALLDIR",
|
|
||||||
"Dangerzone",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
|
# Add the Wix root element
|
||||||
product_el = ET.SubElement(
|
wix_el = ET.Element(
|
||||||
root_el,
|
"Wix",
|
||||||
"Product",
|
{
|
||||||
|
"xmlns": "http://wixtoolset.org/schemas/v4/wxs",
|
||||||
|
"xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the Package element
|
||||||
|
package_el = ET.SubElement(
|
||||||
|
wix_el,
|
||||||
|
"Package",
|
||||||
Name="Dangerzone",
|
Name="Dangerzone",
|
||||||
Manufacturer="Freedom of the Press Foundation",
|
Manufacturer="Freedom of the Press Foundation",
|
||||||
Id="*",
|
UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576",
|
||||||
UpgradeCode="$(var.ProductUpgradeCode)",
|
|
||||||
Language="1033",
|
Language="1033",
|
||||||
Codepage="1252",
|
|
||||||
Version="$(var.ProductVersion)",
|
|
||||||
)
|
|
||||||
ET.SubElement(
|
|
||||||
product_el,
|
|
||||||
"Package",
|
|
||||||
Id="*",
|
|
||||||
Keywords="Installer",
|
|
||||||
Description="Dangerzone $(var.ProductVersion) Installer",
|
|
||||||
Manufacturer="Freedom of the Press Foundation",
|
|
||||||
InstallerVersion="100",
|
|
||||||
Languages="1033",
|
|
||||||
Compressed="yes",
|
Compressed="yes",
|
||||||
SummaryCodepage="1252",
|
Codepage="1252",
|
||||||
|
Version=version,
|
||||||
)
|
)
|
||||||
ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
|
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
|
package_el,
|
||||||
|
"SummaryInformation",
|
||||||
|
Keywords="Installer",
|
||||||
|
Description="Dangerzone " + version + " Installer",
|
||||||
|
Codepage="1252",
|
||||||
)
|
)
|
||||||
ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
|
ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes")
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el,
|
package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
|
||||||
|
)
|
||||||
|
ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
|
||||||
|
ET.SubElement(
|
||||||
|
package_el,
|
||||||
"Property",
|
"Property",
|
||||||
Id="ARPHELPLINK",
|
Id="ARPHELPLINK",
|
||||||
Value="https://dangerzone.rocks",
|
Value="https://dangerzone.rocks",
|
||||||
)
|
)
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el,
|
package_el,
|
||||||
"Property",
|
"Property",
|
||||||
Id="ARPURLINFOABOUT",
|
Id="ARPURLINFOABOUT",
|
||||||
Value="https://freedom.press",
|
Value="https://freedom.press",
|
||||||
)
|
)
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el,
|
package_el,
|
||||||
"Property",
|
|
||||||
Id="WIXUI_INSTALLDIR",
|
|
||||||
Value="INSTALLDIR",
|
|
||||||
)
|
|
||||||
ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir")
|
|
||||||
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
|
|
||||||
ET.SubElement(
|
|
||||||
product_el,
|
|
||||||
"WixVariable",
|
"WixVariable",
|
||||||
Id="WixUILicenseRtf",
|
Id="WixUILicenseRtf",
|
||||||
Value="..\\install\\windows\\license.rtf",
|
Value="..\\install\\windows\\license.rtf",
|
||||||
)
|
)
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el,
|
package_el,
|
||||||
"WixVariable",
|
"WixVariable",
|
||||||
Id="WixUIDialogBmp",
|
Id="WixUIDialogBmp",
|
||||||
Value="..\\install\\windows\\dialog.bmp",
|
Value="..\\install\\windows\\dialog.bmp",
|
||||||
)
|
)
|
||||||
ET.SubElement(
|
ET.SubElement(
|
||||||
product_el,
|
package_el,
|
||||||
"MajorUpgrade",
|
"MajorUpgrade",
|
||||||
AllowSameVersionUpgrades="yes",
|
|
||||||
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
|
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
|
||||||
)
|
)
|
||||||
|
|
||||||
build_dir_xml(product_el, data)
|
# Add The UI element
|
||||||
component_ids = build_components_xml(product_el, data)
|
ui_el = ET.SubElement(package_el, "UI")
|
||||||
|
ET.SubElement(
|
||||||
|
ui_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER"
|
||||||
|
)
|
||||||
|
ET.SubElement(ui_el, "UIRef", Id="WixUI_ErrorProgressText")
|
||||||
|
|
||||||
feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
|
# Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous
|
||||||
for component_id in component_ids:
|
# version of Dangerzone is not uninstalled during the upgrade by checking if the older installation
|
||||||
ET.SubElement(feature_el, "ComponentRef", Id=component_id)
|
# exists in "C:\Program Files (x86)\Dangerzone".
|
||||||
|
#
|
||||||
|
# Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location
|
||||||
|
# during install by checking if the registry key for it exists.
|
||||||
|
#
|
||||||
|
# Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch.
|
||||||
|
# In this case the installer errors until Dangerzone 0.8.0 is uninstalled again
|
||||||
|
#
|
||||||
|
# TODO: Revert this once we are reasonably certain there aren't too many affected Dangerzone installations.
|
||||||
|
property_el = ET.SubElement(package_el, "Property", Id="OLDDANGERZONEFOUND")
|
||||||
|
dir_search_el = ET.SubElement(
|
||||||
|
property_el,
|
||||||
|
"DirectorySearch",
|
||||||
|
Id="dangerzone_install_folder",
|
||||||
|
Path="C:\\Program Files (x86)\\Dangerzone",
|
||||||
|
)
|
||||||
|
registry_search_el = ET.SubElement(package_el, "Property", Id="DANGERZONE080FOUND")
|
||||||
|
ET.SubElement(
|
||||||
|
registry_search_el,
|
||||||
|
"RegistrySearch",
|
||||||
|
Root="HKLM",
|
||||||
|
Key="SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{03C2D2B2-9955-4AED-831F-DA4E67FC0FDB}",
|
||||||
|
Name="DisplayName",
|
||||||
|
Type="raw",
|
||||||
|
)
|
||||||
|
ET.SubElement(dir_search_el, "FileSearch", Name="dangerzone.exe")
|
||||||
|
ET.SubElement(
|
||||||
|
package_el,
|
||||||
|
"Launch",
|
||||||
|
Condition="NOT OLDDANGERZONEFOUND AND NOT DANGERZONE080FOUND",
|
||||||
|
Message="A previous version of [ProductName] is already installed. Please uninstall it from Programs and Features before proceeding with the installation.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the ProgramMenuFolder StandardDirectory
|
||||||
|
programmenufolder_el = ET.SubElement(
|
||||||
|
package_el,
|
||||||
|
"StandardDirectory",
|
||||||
|
Id="ProgramMenuFolder",
|
||||||
|
)
|
||||||
|
# Add a shortcut for Dangerzone in the Start menu
|
||||||
|
shortcut_el = ET.SubElement(
|
||||||
|
programmenufolder_el,
|
||||||
|
"Component",
|
||||||
|
Id="ApplicationShortcuts",
|
||||||
|
Guid="539E7DE8-A124-4C09-AA55-0DD516AAD7BC",
|
||||||
|
)
|
||||||
|
ET.SubElement(
|
||||||
|
shortcut_el,
|
||||||
|
"Shortcut",
|
||||||
|
Id="DangerzoneStartMenuShortcut",
|
||||||
|
Name="Dangerzone",
|
||||||
|
Description="Dangerzone",
|
||||||
|
Target="[INSTALLFOLDER]dangerzone.exe",
|
||||||
|
WorkingDirectory="INSTALLFOLDER",
|
||||||
|
)
|
||||||
|
ET.SubElement(
|
||||||
|
shortcut_el,
|
||||||
|
"RegistryValue",
|
||||||
|
Root="HKCU",
|
||||||
|
Key="Software\\Freedom of the Press Foundation\\Dangerzone",
|
||||||
|
Name="installed",
|
||||||
|
Type="integer",
|
||||||
|
Value="1",
|
||||||
|
KeyPath="yes",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the ProgramFilesFolder StandardDirectory
|
||||||
|
programfilesfolder_el = ET.SubElement(
|
||||||
|
package_el,
|
||||||
|
"StandardDirectory",
|
||||||
|
Id="ProgramFiles64Folder",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the directory structure for the installed product
|
||||||
|
build_directory_xml(programfilesfolder_el, data)
|
||||||
|
|
||||||
|
# Create a component group for application components
|
||||||
|
applicationcomponents_el = ET.SubElement(
|
||||||
|
package_el, "ComponentGroup", Id="ApplicationComponents"
|
||||||
|
)
|
||||||
|
# Populate the application components group with components for the installed package
|
||||||
|
build_components_xml(applicationcomponents_el, data)
|
||||||
|
|
||||||
|
# Add the Feature element
|
||||||
|
feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1")
|
||||||
|
ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents")
|
||||||
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
|
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
|
||||||
|
|
||||||
print('<?xml version="1.0" encoding="windows-1252"?>')
|
ET.indent(wix_el, space=" ")
|
||||||
print(f'<?define ProductVersion = "{version}"?>')
|
|
||||||
print('<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>')
|
with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file:
|
||||||
ET.indent(root_el)
|
wxs_file.write(ET.tostring(wix_el).decode())
|
||||||
print(ET.tostring(root_el).decode())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
92
poetry.lock
generated
92
poetry.lock
generated
|
@ -11,6 +11,28 @@ files = [
|
||||||
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.6.2.post1"
|
||||||
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"},
|
||||||
|
{file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||||
|
idna = ">=2.8"
|
||||||
|
sniffio = ">=1.1"
|
||||||
|
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||||
|
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"]
|
||||||
|
trio = ["trio (>=0.26.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "appdirs"
|
name = "appdirs"
|
||||||
version = "1.4.4"
|
version = "1.4.4"
|
||||||
|
@ -404,6 +426,63 @@ files = [
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["pytest (>=6)"]
|
test = ["pytest (>=6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.14.0"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpcore"
|
||||||
|
version = "1.0.7"
|
||||||
|
description = "A minimal low-level HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
|
||||||
|
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = "*"
|
||||||
|
h11 = ">=0.13,<0.15"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
trio = ["trio (>=0.22.0,<1.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpx"
|
||||||
|
version = "0.27.2"
|
||||||
|
description = "The next generation HTTP client."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
|
||||||
|
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = "*"
|
||||||
|
certifi = "*"
|
||||||
|
httpcore = "==1.*"
|
||||||
|
idna = "*"
|
||||||
|
sniffio = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotli", "brotlicffi"]
|
||||||
|
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||||
|
http2 = ["h2 (>=3,<5)"]
|
||||||
|
socks = ["socksio (==1.*)"]
|
||||||
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
|
@ -991,6 +1070,17 @@ files = [
|
||||||
{file = "shiboken6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:b11e750e696bb565d897e0f5836710edfb86bd355f87b09988bd31b2aad404d3"},
|
{file = "shiboken6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:b11e750e696bb565d897e0f5836710edfb86bd355f87b09988bd31b2aad404d3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
description = "Sniff out which async library your code is running under"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||||
|
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strip-ansi"
|
name = "strip-ansi"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -1099,4 +1189,4 @@ type = ["pytest-mypy"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.13"
|
python-versions = ">=3.9,<3.13"
|
||||||
content-hash = "44356eaeeb3dbe23b922413ee68f8c73c6ce8ebbdee8a80afecb410e060d0382"
|
content-hash = "5d1ff28aa04c3a814280e55c0b2a307efe5ca953cd4cb281056c35fd2e53fdf0"
|
||||||
|
|
|
@ -57,6 +57,9 @@ pytest-rerunfailures = "^14.0"
|
||||||
[tool.poetry.group.container.dependencies]
|
[tool.poetry.group.container.dependencies]
|
||||||
pymupdf = "1.24.11" # Last version to support python 3.8 (needed for Ubuntu Focal support)
|
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"
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
skip_gitignore = true
|
skip_gitignore = true
|
||||||
|
|
Loading…
Reference in a new issue