Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
686 changes: 686 additions & 0 deletions .github/scripts/memory_report.py

Large diffs are not rendered by default.

878 changes: 878 additions & 0 deletions .github/scripts/test_memory_report.py

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions .github/workflows/capacitor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,20 @@ jobs:
- name: Build the end-to-end test build
run: npm run build:e2e
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash ../web/e2e/run.sh "$scenario" "$PWD/node_modules/electron/dist/electron" "$PWD/dist/app-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-capacitor
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Build the release
run: |
npm run build
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ jobs:
- name: Build the application image
run: gradle --console=plain createDistributable
- name: Smoke test the application image and measure its memory
env:
# The measurement writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
binary="$PWD/build/compose/binaries/main/app/vfox-plus/bin/vfox-plus"
xvfb-run -a -s "-screen 0 1280x800x24" "$binary" > app.log 2>&1 &
Expand All @@ -118,6 +121,12 @@ jobs:
fi
pkill -f "^$binary"
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- bash ../shared/tools/measure.sh "$binary"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-compose
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Build the .deb and the tarball
run: |
# Reproducible timestamps in the package metadata.
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/dioxus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,20 @@ jobs:
cargo build --release --locked --features e2e
cp target/release/vfox-plus "$RUNNER_TEMP/vfox-plus-e2e"
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-dioxus
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Build the release
working-directory: dioxus
run: cargo build --release --locked
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/flet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,21 @@ jobs:
done
- name: Run the end-to-end scenarios in a virtual display
working-directory: .
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
env VFOX_PLUS_E2E_TIMEOUT=300 VFOX_PLUS_E2E_VFOX="$VFOX_PLUS_E2E_VFOX" \
bash web/e2e/run.sh "$scenario" "$PWD/flet/build/linux/vfox-plus"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-flet
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Smoke test the release
run: |
binary="$PWD/build/linux/vfox-plus"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ jobs:
run: |
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
python3 tool/measure.py --output "$RUNNER_TEMP/measure" build/linux/x64/release/bundle/vfox-plus
# The numbers, for memory.yml.
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-flutter
path: ${{ runner.temp }}/measure/memory.txt
if-no-files-found: error
overwrite: true
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: failure()
with:
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/gio.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,21 @@ jobs:
tar -xzf "$RUNNER_TEMP/$archive.tar.gz" -C "$RUNNER_TEMP"
echo "VFOX_PLUS_E2E_VFOX=$RUNNER_TEMP/$archive/vfox" >> "$GITHUB_ENV"
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
bash gio/tools/build.sh --e2e "$RUNNER_TEMP/vfox-plus-e2e"
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-gio
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Smoke test the release
run: |
binary="$PWD/gio/build/vfox-plus"
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/memory.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright 2026 Han Li and contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The memory of the implementations in a pull request. The workflow of each
# implementation measures its application and uploads the result as the
# artifact memory-<implementation>. When the last of those workflows has ended
# for the latest commit of a pull request, this one writes a single comment on
# it: a table like the one of docs/memory.md with the difference from the latest
# numbers of main. Later runs replace that comment instead of adding another one
# (.github/scripts/memory_report.py; docs/memory.md#memory-in-pull-requests).
#
# It runs after the workflows of the pull request, in the context of the default
# branch, so that it can write to pull requests of forks, which a pull_request
# workflow cannot. For the same reason it never runs code of the pull request: it
# checks out main, and the artifacts it downloads are data, of which the script
# only reads numbers.
#
# Run it by hand to write the report of a pull request again:
# gh workflow run memory.yml -f pull_request=<number>
name: Memory

on:
workflow_run:
# The workflows that measure an implementation, by name. Keep this in sync
# with MEMORY_IMPLEMENTATIONS below, which has their file names.
workflows: [Capacitor, Compose, Dioxus, Flet, Flutter, Gio, mygo, Tauri, Uno, Wails]
types: [completed]
workflow_dispatch:
inputs:
pull_request:
description: The number of the pull request to write the report of now
required: true
type: number

permissions:
actions: read
contents: read
pull-requests: write

concurrency:
# The workflows of a commit end one after another, and each starts a report.
# They run one at a time, so that two of them cannot both write the comment,
# and the last one to start is the one that finds all of them ended.
group: memory-${{ github.event.workflow_run.head_sha || format('pull-request-{0}', inputs.pull_request) }}
cancel-in-progress: false

env:
# The file names of the workflows of the implementations (without .yml), which
# are also the names of their directories and of their artifacts.
MEMORY_IMPLEMENTATIONS: capacitor compose dioxus flet flutter gio mygo tauri uno wails

jobs:
report:
name: Write the report
# Forks do not run CI.
if: >-
github.repository == 'version-fox/vfox-plus' &&
(github.event_name == 'workflow_dispatch' || github.event.workflow_run.event == 'pull_request')
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Write the report
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
PULL_REQUEST: ${{ inputs.pull_request }}
run: |
if [[ -n "$RUN_ID" ]]; then
python3 .github/scripts/memory_report.py --run "$RUN_ID"
else
python3 .github/scripts/memory_report.py --pull-request "$PULL_REQUEST"
fi
9 changes: 9 additions & 0 deletions .github/workflows/mygo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,21 @@ jobs:
tar -xzf "$RUNNER_TEMP/$archive.tar.gz" -C "$RUNNER_TEMP"
echo "VFOX_PLUS_E2E_VFOX=$RUNNER_TEMP/$archive/vfox" >> "$GITHUB_ENV"
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
bash mygo/tools/build.sh --e2e "$RUNNER_TEMP/vfox-plus-e2e"
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-mygo
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Smoke test the release
run: |
binary="$PWD/mygo/build/vfox-plus"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/repository.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ jobs:
for file in shared/icons.json shared/i18n/*.arb shared/fixtures/*/cases.json; do
jq empty "$file" || { echo "::error file=$file::Invalid JSON"; exit 1; }
done
- name: Test the memory report
# What memory.yml writes on pull requests.
run: python3 -m unittest discover -s .github/scripts -p 'test_*.py'
9 changes: 9 additions & 0 deletions .github/workflows/tauri.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,20 @@ jobs:
'{"build": {"frontendDist": "../../web/dist/tauri-e2e", "beforeBuildCommand": ""}}'
cp src-tauri/target/release/vfox-plus "$RUNNER_TEMP/vfox-plus-e2e"
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-tauri
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Build the release
working-directory: tauri
run: npx tauri build --no-bundle
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/uno.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,21 @@ jobs:
working-directory: uno
run: dotnet restore src/VfoxPlus.App --runtime linux-x64 -p:Configuration=Release
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
bash uno/tools/publish.sh --e2e "$RUNNER_TEMP/vfox-plus-e2e"
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e/VfoxPlus"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-uno
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Publish the release
run: bash uno/tools/publish.sh uno/build/stage
- name: Build the .deb and the tarball
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/wails.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,21 @@ jobs:
tar -xzf "$RUNNER_TEMP/$archive.tar.gz" -C "$RUNNER_TEMP"
echo "VFOX_PLUS_E2E_VFOX=$RUNNER_TEMP/$archive/vfox" >> "$GITHUB_ENV"
- name: Run the end-to-end scenarios in a virtual display
env:
# The "measure" scenario writes its numbers here for memory.yml.
VFOX_PLUS_MEASURE_OUTPUT: ${{ runner.temp }}/memory/memory.txt
run: |
bash wails/build.sh --e2e "$RUNNER_TEMP/vfox-plus-e2e"
for scenario in fake real measure; do
xvfb-run -a -s "-screen 0 1280x800x24" dbus-run-session -- \
bash web/e2e/run.sh "$scenario" "$RUNNER_TEMP/vfox-plus-e2e"
done
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: memory-wails
path: ${{ runner.temp }}/memory/memory.txt
if-no-files-found: error
overwrite: true
- name: Smoke test the release
run: |
binary="$PWD/wails/build/vfox-plus"
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ Thumbs.db

# Packaging output (see flutter/packaging/linux/build_deb.sh).
/dist/

# Python bytecode, which running the tests of .github/scripts leaves behind. CI
# rejects binary files.
__pycache__/
*.pyc
9 changes: 8 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ all of them.

| Workflow | Runs on | Checks |
| --- | --- | --- |
| [`repository.yml`](.github/workflows/repository.yml) | every change | License headers (SkyWalking Eyes), workflows (actionlint), shell scripts (ShellCheck), shared JSON, no committed binary files |
| [`repository.yml`](.github/workflows/repository.yml) | every change | License headers (SkyWalking Eyes), workflows (actionlint), shell scripts (ShellCheck), shared JSON, no committed binary files, the tests of the memory report |
| [`memory.yml`](.github/workflows/memory.yml) | the end of the workflows of the implementations, on pull requests | One comment on the pull request with the memory of every implementation, sorted like [docs/memory.md](docs/memory.md#results), and the difference from `main` (see [Memory in pull requests](docs/memory.md#memory-in-pull-requests)) |
| [`flutter.yml`](.github/workflows/flutter.yml) | `flutter/`, `shared/` | Lock file, formatting, generated translations, analysis, tests with an 85 % coverage gate, end-to-end tests on Linux under Xvfb against captured output and against the real vfox, release build with a smoke test and a memory measurement, `.deb` and `.tar.gz` packages validated with lintian |
| [`flutter-sandbox.yml`](.github/workflows/flutter-sandbox.yml) | `flutter/sandbox/`, `flutter/versions.env` | Builds the Docker sandbox and runs `sandbox.sh all` in it |
| [`web.yml`](.github/workflows/web.yml) | `web/`, `shared/` | Formatting (Prettier), lint (ESLint), type checking, unit tests with an 85 % coverage gate, the production builds |
Expand All @@ -71,6 +72,12 @@ all of them.
Dependabot keeps the pub, npm, cargo, Go, Gradle and NuGet dependencies and the
GitHub Actions up to date; actions are pinned to commit SHAs.

A pull request that rebuilds an implementation gets a comment with the memory of all of
them and the difference from `main`, which the next commit replaces instead of adding
another one. A change that is more than the noise between CI runs is in bold; read
[Memory in pull requests](docs/memory.md#memory-in-pull-requests) before you trust a small
one.

## Common changes

### Texts
Expand Down
Loading
Loading