Compare commits

..

No commits in common. "098993f81c596ec452fe2b4f613077a731029c90" and "9d1c0ef9c6a30405b7e6869344b58ce92f5b06a5" have entirely different histories.

18 changed files with 137 additions and 3313 deletions

View File

@ -1,18 +1,14 @@
name: Build Avida-ED Native Apps
name: Build Avida-ED Onefile
on:
workflow_dispatch:
inputs:
ed_versions:
description: "Comma-separated versions to build, for example v3,v4"
description: "Comma-separated versions (v3,v4)"
default: "v3,v4"
required: true
push:
branches: [main]
jobs:
build:
name: ${{ matrix.ver }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
@ -22,97 +18,63 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Skip unrequested versions
id: version_filter
shell: bash
run: |
requested=",${{ github.event.inputs.ed_versions || 'v3,v4' }},"
requested="${requested// /}"
if [[ "$requested" == *",${{ matrix.ver }},"* ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
- name: Set up Rust
if: steps.version_filter.outputs.enabled == 'true'
uses: dtolnay/rust-toolchain@stable
- name: Install Linux packaging dependencies
if: steps.version_filter.outputs.enabled == 'true' && runner.os == 'Linux'
- name: Fetch assets via docker compose (using URL fallback on mac/win if docker unavailable)
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
imagemagick \
file \
desktop-file-utils
curl -L -o appimagetool \
https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
sudo mv appimagetool /usr/local/bin/appimagetool
- name: Install Windows packaging dependencies
if: steps.version_filter.outputs.enabled == 'true' && runner.os == 'Windows'
shell: powershell
run: |
choco install 7zip -y --no-progress
- name: Fetch canonical Avida-ED assets
if: steps.version_filter.outputs.enabled == 'true'
shell: bash
run: |
MODE=github ED_VER=${{ matrix.ver }} OUTDIR=apps/${{ matrix.ver }} bash tools/fetch_assets.sh
if command -v docker >/dev/null 2>&1; then
docker compose run --rm fetch-${{ matrix.ver }}
else
MODE=url URL=https://avida-ed.msu.edu/app4/ OUTDIR=./apps/${{ matrix.ver }} bash tools/fetch_assets.sh
fi
- name: Inject webroot
if: steps.version_filter.outputs.enabled == 'true'
shell: bash
run: |
bash tools/inject_webroot.sh ${{ matrix.ver }}
rm -rf server-ui/webroot
mkdir -p server-ui/webroot
rsync -a apps/${{ matrix.ver }}/Avida-ED-Eco/ server-ui/webroot/Avida-ED-Eco/
- name: Build binary
if: steps.version_filter.outputs.enabled == 'true'
shell: bash
run: |
case "${{ matrix.ver }}" in
v3) default_path="/Avida-ED/index.html" ;;
v4) default_path="/Avida-ED-Eco/index.html" ;;
esac
cd server-ui
AVIDA_ED_DEFAULT_PATH="$default_path" cargo build --release
cargo build --release
- name: Package Linux AppImage
if: steps.version_filter.outputs.enabled == 'true' && runner.os == 'Linux'
shell: bash
- name: Package (Linux AppImage)
if: startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update && sudo apt-get install -y imagemagick || true
bash packaging/linux/make_appimage.sh ${{ matrix.ver }} server-ui/target/release/avidaed_onefile
mv Avida-ED-${{ matrix.ver }}-Linux-x86_64.AppImage Avida-ED-${{ matrix.ver }}-Linux-x86_64.AppImage
- name: Package Windows app
if: steps.version_filter.outputs.enabled == 'true' && runner.os == 'Windows'
- name: Package (Windows EXE)
if: startsWith(matrix.os, 'windows')
shell: powershell
run: |
$WV2 = "$env:RUNNER_TEMP\WebView2Fixed" # pre-cached in your org, or downloaded here
# TODO: fetch/unzip WebView2 Fixed into $WV2 if not cached
powershell -ExecutionPolicy Bypass -File packaging/windows/make_windows_sfx.ps1 `
-Version ${{ matrix.ver }} `
-BinPath server-ui/target/release/avidaed_onefile.exe `
-OutputDir .
-WV2Fixed $WV2
Move-Item packaging\windows\Avida-ED-${{ matrix.ver }}-Windows.exe .
- name: Package macOS app
if: steps.version_filter.outputs.enabled == 'true' && runner.os == 'macOS'
shell: bash
- name: Package (macOS .app)
if: startsWith(matrix.os, 'macos')
run: |
bash packaging/mac/make_macos_bundle.sh ${{ matrix.ver }} server-ui/target/release/avidaed_onefile .
bash packaging/mac/make_macos_bundle.sh ${{ matrix.ver }} server-ui/target/release/avidaed_onefile
# Optionally make a DMG
# brew install create-dmg
# create-dmg "Avida-ED-${{ matrix.ver }}.app"
- name: Upload artifacts
if: steps.version_filter.outputs.enabled == 'true'
uses: actions/upload-artifact@v4
with:
name: Avida-ED-${{ matrix.ver }}-${{ matrix.os }}
path: |
Avida-ED-${{ matrix.ver }}-Linux-*.AppImage
Avida-ED-${{ matrix.ver }}-Windows-*.zip
Avida-ED-${{ matrix.ver }}-Windows-*.exe
Avida-ED-${{ matrix.ver }}-macOS.zip
if-no-files-found: error
*.AppImage
*.exe
Avida-ED-${{ matrix.ver }}.app
if-no-files-found: ignore

12
.gitignore vendored
View File

@ -211,15 +211,3 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Avida-ED-App-Builder generated artifacts
/apps/
/server-ui/webroot/
/AvidaED-*.AppDir/
/squashfs-root/
/Avida-ED-*-Linux-*.AppImage
/Avida-ED-*-Windows-*.zip
/Avida-ED-*-Windows-*.exe
/Avida-ED-*.app/
/Avida-ED-*-macOS.zip
/packaging/windows/payload_*/
/packaging/windows/payload_*.7z

View File

@ -1,44 +1,26 @@
VER ?= v4
APP_DIR_v3 := Avida-ED
APP_DIR_v4 := Avida-ED-Eco
APP_DIR := $(APP_DIR_$(VER))
DEFAULT_PATH := /$(APP_DIR)/index.html
VER ?= v4 # or v3
.PHONY: fetch-v3 fetch-v4 fetch-v3-url fetch-v4-url check-ver check-assets check-linux-deps inject-$(VER) build-linux build-mac build-win appimage winexe macapp all
.PHONY: fetch-$(VER) inject-$(VER) build-linux build-mac build-win appimage winexe macapp all
fetch-v3:
MODE=github ED_VER=v3 OUTDIR=apps/v3 bash tools/fetch_assets.sh
docker compose run --rm fetch-v3
fetch-v4:
MODE=github ED_VER=v4 OUTDIR=apps/v4 bash tools/fetch_assets.sh
fetch-v3-url:
MODE=url ED_VER=v3 URL=https://avida-ed.msu.edu/app/ OUTDIR=apps/v3 bash tools/fetch_assets.sh
fetch-v4-url:
MODE=url ED_VER=v4 URL=https://avida-ed.github.io/Avida-ED-Eco/ OUTDIR=apps/v4 bash tools/fetch_assets.sh
docker compose run --rm fetch-v4
check-ver:
@test -n "$(APP_DIR)" || { echo "Unsupported VER=$(VER). Use VER=v3 or VER=v4."; exit 2; }
check-assets: check-ver
@test -f "apps/$(VER)/$(APP_DIR)/index.html" || { echo "Missing apps/$(VER)/$(APP_DIR)/index.html. Run make fetch-$(VER) first."; exit 2; }
check-linux-deps:
@command -v cargo >/dev/null || { echo "Missing cargo. Install Rust 1.75+ with rustup."; exit 127; }
@command -v pkg-config >/dev/null || { echo "Missing pkg-config. Install pkg-config plus GTK/WebKitGTK development packages."; exit 127; }
@pkg-config --exists glib-2.0 gtk+-3.0 || { echo "Missing GLib/GTK development packages visible to pkg-config."; exit 127; }
@pkg-config --exists webkit2gtk-4.1 || pkg-config --exists webkit2gtk-4.0 || { echo "Missing WebKitGTK development package visible to pkg-config."; exit 127; }
inject-$(VER): check-assets
bash tools/inject_webroot.sh $(VER)
inject-$(VER):
rm -rf server-ui/webroot
mkdir -p server-ui/webroot
rsync -a apps/$(VER)/Avida-ED-Eco/ server-ui/webroot/Avida-ED-Eco/
# Build binaries natively on each OS runner
build-linux: check-ver check-linux-deps inject-$(VER)
cd server-ui && AVIDA_ED_DEFAULT_PATH="$(DEFAULT_PATH)" cargo build --release
build-linux: inject-$(VER)
cd server-ui && cargo build --release
build-mac: inject-$(VER)
cd server-ui && AVIDA_ED_DEFAULT_PATH="$(DEFAULT_PATH)" cargo build --release
cd server-ui && cargo build --release
build-win: inject-$(VER)
cd server-ui && AVIDA_ED_DEFAULT_PATH="$(DEFAULT_PATH)" cargo build --release
cd server-ui && cargo build --release
# Package
appimage: build-linux
@ -49,14 +31,14 @@ winexe: build-win
powershell -ExecutionPolicy Bypass -File packaging/windows/make_windows_sfx.ps1 \
-Version $(VER) \
-BinPath $(CURDIR)/server-ui/target/release/avidaed_onefile.exe \
-WV2Fixed "C:\SDKs\WebView2.FixedRuntime" \
-OutputDir $(CURDIR)
-WV2Fixed "C:\SDKs\WebView2.FixedRuntime"
macapp: build-mac
bash packaging/mac/make_macos_bundle.sh $(VER) server-ui/target/release/avidaed_onefile $(CURDIR)
bash packaging/mac/make_macos_bundle.sh $(VER) server-ui/target/release/avidaed_onefile
all:
@echo "Targets:"
@echo " make fetch-v3 | fetch-v4"
@echo " make build-linux | build-mac | build-win"
@echo " make appimage | winexe | macapp"

View File

@ -58,7 +58,7 @@ avidaed-onefile/
| OS | Additional Requirements |
| ----------- | ----------------------------------------------------------------- |
| **Linux** | `pkg-config`, `libgtk-3-dev`, `libwebkit2gtk-4.1-dev` or `libwebkit2gtk-4.0-dev`, `appimagetool` for AppImage packaging |
| **Linux** | `libgtk-3-dev`, `libwebkit2gtk-4.0-dev`, `appimagetool` |
| **Windows** | PowerShell 5+, 7-Zip installed in `C:\Program Files\7-Zip\7z.exe` |
| **macOS** | Xcode command-line tools, optionally `create-dmg` |
@ -66,26 +66,24 @@ avidaed-onefile/
## 🧱 Step 1. Fetch the Avida-ED Web Assets
Avida-ED web assets are pulled from the canonical GitHub repositories:
Avida-ED web builds are pulled either:
* v3: `https://github.com/Avida-ED/Avida-ED3`
* v4: `https://github.com/Avida-ED/Avida-ED4`
* from **a known-good Docker image** (`welsberr/aed-docker`), or
* from a **public URL** (fallback).
### Option A — Using GitHub (canonical)
### Option A — Using Docker (preferred)
```bash
# Pull version 3 and version 4 assets
make fetch-v3
make fetch-v4
docker compose run --rm fetch-v3
docker compose run --rm fetch-v4
```
This clones the selected canonical source repository and normalizes the app root into:
This:
```
apps/
├── v3/Avida-ED/index.html
└── v4/Avida-ED-Eco/index.html
```
* spins up the minimal Alpine container in `tools/Dockerfile.fetch`,
* extracts the Avida-ED webroot from the containers Nginx image, and
* deposits the result under `apps/v3/` and `apps/v4/`.
You should see:
@ -95,12 +93,12 @@ apps/
└── v4/Avida-ED-Eco/index.html
```
### Option B — Fetch via Public Site (fallback)
### Option B — Fetch via HTTPS (fallback)
If GitHub is unavailable but the public app site is reachable:
If Docker is unavailable:
```bash
make fetch-v4-url
MODE=url URL=https://avida-ed.msu.edu/app4/ OUTDIR=./apps/v4 bash tools/fetch_assets.sh
```
---
@ -129,8 +127,6 @@ The binary (`avidaed_onefile`) runs both the HTTP server and browser window.
make VER=v4 build-linux # or build-mac
```
On Linux, `build-linux` checks for Rust, `pkg-config`, GTK, and WebKitGTK development packages before compiling. Building the binary does not require `appimagetool`; only `make VER=v4 appimage` does.
### Windows (PowerShell)
```powershell
@ -178,15 +174,15 @@ chmod +x Avida-ED-v4-Linux-x86_64.AppImage
### 🪟 Windows — Self-Extracting EXE
**Purpose:** a native Windows package containing:
**Purpose:** one `.exe` that includes:
* the app binary,
* an optional WebView2 Fixed Runtime,
* the WebView2 Fixed Runtime,
* a batch launcher.
Steps:
1. Optional: download the **WebView2 Fixed Version Runtime** (x64) from Microsoft:
1. Download the **WebView2 Fixed Version Runtime** (x64) from Microsoft:
[https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section)
2. Extract it somewhere, e.g. `C:\SDKs\WebView2.FixedRuntime`.
3. Run:
@ -207,15 +203,14 @@ Steps:
Result:
```
Avida-ED-v4-Windows-x64.zip
Avida-ED-v4-Windows-x64.exe # when 7-Zip's SFX module is available
Avida-ED-v4-Windows.exe
```
When run:
* `run.bat` sets `WEBVIEW2_BROWSER_EXECUTABLE_FOLDER` when a fixed runtime is bundled.
* If no fixed runtime is bundled, the system WebView2 Runtime is used.
* The SFX executable self-extracts and launches `run.bat`.
* Extracts to `%TEMP%\Avida-ED-*`,
* Sets `WEBVIEW2_BROWSER_EXECUTABLE_FOLDER`,
* Launches immediately.
---
@ -243,12 +238,6 @@ brew install create-dmg
create-dmg Avida-ED-v4.app
```
The bundle script also writes:
```
Avida-ED-v4-macOS.zip
```
---
## 🧪 Step 5. Test All Artifacts
@ -277,22 +266,11 @@ make all
On CI (GitHub Actions), artifacts are built via matrix:
* OS: `ubuntu-22.04`, `windows-2022`, `macos-13`
* OS: `ubuntu`, `windows`, `macos`
* Version: `v3`, `v4`
Resulting artifacts are automatically uploaded.
The workflow is `.github/workflows/release.yml`. It:
* fetches canonical assets from `Avida-ED/Avida-ED3` and `Avida-ED/Avida-ED4`,
* injects the correct app root for each version,
* builds the Rust wrapper on the native runner,
* packages Windows as a zip plus optional SFX executable,
* packages macOS as a `.app` bundle inside a zip,
* packages Linux as an AppImage.
Run it manually from GitHub Actions with the default `v3,v4` input, or enter a single version such as `v4`.
---
## 🔁 Updating Avida-ED Versions
@ -396,3 +374,5 @@ This repository's content was largely derived from prompting
OpenAI's ChatGPT with GPT 5.
---

View File

@ -1,64 +0,0 @@
# Avida-ED App Test Plan
This builder packages the browser apps, so tests should cover two separate layers:
1. Browser app behavior in the canonical repositories.
2. Packaged executable behavior in this builder.
## Canonical App Tests
The canonical v3 and v4 repositories should own browser-level behavior tests. Current coverage:
- Load the app through Playwright with `?avidaTest=1`.
- Wait for the Avida worker to start.
- Import a minimal default experiment.
- Verify `webGridData` and `webPopulationStats` messages arrive.
- Assert no browser errors were captured.
- Exercise the missing-parent-series population-stats regression directly.
Next browser tests to add:
- Inject an ancestor sequence, step several updates, and verify population count changes.
- Trace an organism with `webOrgTraceBySequence` and verify snapshots are produced.
- Simulate the freezer offspring delete/rename path that previously caused stale DOM removal.
- Import a saved workspace containing multiple ancestors and verify all injection responses are handled.
- Export CSV from empty and populated page states.
## Packaged App Tests
The packaged executable should be validated without depending on visual inspection. The Rust wrapper already logs:
- Local HTTP server URL.
- HTTP 200/404 responses.
- WebView page-load start/finish.
- JavaScript errors, unhandled rejections, and proxied `console.error` output.
Use `tests/smoke_appimage.sh` to run a built AppImage briefly, capture those signals, and fail if the app never loads or JavaScript errors appear.
Recommended Linux smoke matrix:
- `Avida-ED-v3-Linux-x86_64.AppImage`
- `Avida-ED-v4-Linux-x86_64.AppImage`
- Run under `xvfb-run` in CI when no display is available.
- Run once with software GL environment variables for older GPUs:
`LIBGL_ALWAYS_SOFTWARE=1 WEBKIT_DISABLE_COMPOSITING_MODE=1`.
The AppImage smoke test intentionally treats timeout exit as success after the app has loaded, because the app is expected to keep running until the window is closed.
Recommended Windows smoke matrix:
- `Avida-ED-v3-Windows-x64.zip`
- `Avida-ED-v4-Windows-x64.zip`
- Verify `run.bat`, `avidaed_onefile.exe`, and `README.txt` are present.
- Launch `run.bat` on a Windows VM with WebView2 Runtime installed.
- Repeat with a bundled WebView2 Fixed Runtime when available.
Recommended macOS smoke matrix:
- `Avida-ED-v3-macOS.zip`
- `Avida-ED-v4-macOS.zip`
- Verify the zip contains `Avida-ED-v*.app/Contents/MacOS/Avida-ED`.
- Launch with `open Avida-ED-v*.app` on a clean macOS VM.
- Check Console or stderr for `Avida-ED page-load finished` and no proxied JavaScript errors.
The native GitHub Actions workflow builds and uploads these artifacts, but it does not replace VM launch testing. Use the uploaded artifacts as the input to manual or future automated OS-level smoke tests.

1
packaging/linux/AppRun → packaging/linux/Apprun Executable file → Normal file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env bash
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/usr/bin/avidaed_onefile"

View File

@ -1,23 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
VER="${1:?version folder, e.g., v4}"
BINPATH="${2:-server-ui/target/release/avidaed_onefile}"
case "$VER" in
v3) APP_ASSET_DIR="apps/v3/Avida-ED" ;;
v4) APP_ASSET_DIR="apps/v4/Avida-ED-Eco" ;;
*) echo "error: unsupported version: $VER" >&2; exit 2 ;;
esac
if ! command -v appimagetool >/dev/null 2>&1; then
echo "error: appimagetool is required to build the Linux AppImage" >&2
echo "Install appimagetool or run only 'make VER=${VER} build-linux' to build the Linux binary." >&2
exit 127
fi
if [ ! -x "$BINPATH" ]; then
echo "error: Linux binary not found or not executable: $BINPATH" >&2
exit 2
fi
BINPATH="${2:-../..//server-ui/target/release/avidaed_onefile}"
APPDIR="AvidaED-${VER}.AppDir"
rm -rf "$APPDIR"; mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/icons/hicolor/256x256/apps"
@ -26,19 +10,9 @@ cp "$(dirname "$0")/AppRun" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"
cp "$(dirname "$0")/desktop/avidaed.desktop" "$APPDIR/avidaed.desktop"
if [ -f "$APP_ASSET_DIR/avida-ed-icon.png" ]; then
cp "$APP_ASSET_DIR/avida-ed-icon.png" "$APPDIR/avidaed.png"
else
convert -size 256x256 xc:white "$APPDIR/avidaed.png" 2>/dev/null || true
fi
cp "$APPDIR/avidaed.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/avidaed.png"
# placeholder icon; replace with your PNG
convert -size 256x256 xc:white "$APPDIR/usr/share/icons/hicolor/256x256/apps/avidaed.png" 2>/dev/null || true
APPIMAGETOOL_ARGS=()
if [ -n "${APPIMAGETOOL_RUNTIME_FILE:-}" ]; then
APPIMAGETOOL_ARGS+=(--runtime-file "$APPIMAGETOOL_RUNTIME_FILE")
fi
appimagetool "$APPDIR" "Avida-ED-${VER}-Linux-x86_64.AppImage"
echo "Wrote Avida-ED-${VER}-Linux-x86_64.AppImage"
OUTFILE="Avida-ED-${VER}-Linux-x86_64.AppImage"
rm -f "$OUTFILE"
appimagetool "${APPIMAGETOOL_ARGS[@]}" "$APPDIR" "$OUTFILE"
echo "Wrote $OUTFILE"

16
packaging/mac/make_macos_bundle.sh Executable file → Normal file
View File

@ -2,24 +2,14 @@
set -euo pipefail
VER="${1:?v3|v4}"
BINPATH="${2:-../../server-ui/target/release/avidaed_onefile}"
OUTDIR="${3:-.}"
APP="Avida-ED-${VER}.app"
ZIP="Avida-ED-${VER}-macOS.zip"
rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
cp "$BINPATH" "$APP/Contents/MacOS/Avida-ED"
chmod +x "$APP/Contents/MacOS/Avida-ED"
# Info.plist
sed "s/{{VER}}/${VER}/g" "$(dirname "$0")/info.plist.tmpl" > "$APP/Contents/Info.plist"
sed "s/{{VER}}/${VER}/g" "$(dirname "$0")/Info.plist.tmpl" > "$APP/Contents/Info.plist"
# Icon placeholder
sips -s format icns /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out "$APP/Contents/Resources/AppIcon.icns" >/dev/null 2>&1 || true
if command -v codesign >/dev/null 2>&1; then
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || true
fi
mkdir -p "$OUTDIR"
if command -v ditto >/dev/null 2>&1; then
ditto -c -k --keepParent "$APP" "$OUTDIR/$ZIP"
else
zip -qry "$OUTDIR/$ZIP" "$APP"
fi
echo "Wrote $APP and $OUTDIR/$ZIP"
echo "Wrote $APP (unsigned)."

View File

@ -1,16 +1,32 @@
param(
[Parameter(Mandatory=$true)][string]$Version,
[Parameter(Mandatory=$true)][string]$BinPath,
[string]$WV2Fixed = "",
[string]$SevenZip = "C:\Program Files\7-Zip\7z.exe",
[string]$SfxModule = "C:\Program Files\7-Zip\7z.sfx",
[string]$OutputDir = "."
[Parameter(Mandatory=$true)][string]$Version, # v3 or v4
[Parameter(Mandatory=$true)][string]$BinPath, # path to target\release\avidaed_onefile.exe
[Parameter(Mandatory=$true)][string]$WV2Fixed # path to WebView2 Fixed Runtime folder
)
$Work = Join-Path $PSScriptRoot "payload_$Version"
Remove-Item $Work -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Work | Out-Null
Copy-Item $BinPath -Destination (Join-Path $Work "avidaed_onefile.exe")
Copy-Item $WV2Fixed -Destination (Join-Path $Work "WebView2Fixed") -Recurse
# launcher
$runbat = @"
@echo off
setlocal
set WEBVIEW2_BROWSER_EXECUTABLE_FOLDER=%~dp0WebView2Fixed
start "" "%~dp0avidaed_onefile.exe"
"@
$runbat | Out-File -Encoding ASCII (Join-Path $Work "run.bat")
# create 7z archive
$SevenZip = "C:\Program Files\7-Zip\7z.exe"
& $SevenZip a -t7z (Join-Path $PSScriptRoot "payload_$Version.7z") "$Work\*"
# concatenate SFX + config + archive -> one EXE
$Sfx = Join-Path $PSScriptRoot "7z.sfx"
$Cfg = Join-Path $PSScriptRoot "config.txt"
$Out = Join-Path $PSScriptRoot ("Avida-ED-$Version-Windows.exe")
Get-Content $Sfx -Encoding Byte, $Cfg -Encoding Byte, (Join-Path $PSScriptRoot "payload_$Version.7z") -Encoding Byte |
Set-Content $Out -Encoding Byte
Write-Host "Wrote $Out"
& (Join-Path $PSScriptRoot "make_windows_sfx.ps1") `
-Version $Version `
-BinPath $BinPath `
-WV2Fixed $WV2Fixed `
-SevenZip $SevenZip `
-SfxModule $SfxModule `
-OutputDir $OutputDir

View File

@ -1,79 +0,0 @@
param(
[Parameter(Mandatory=$true)][string]$Version,
[Parameter(Mandatory=$true)][string]$BinPath,
[string]$WV2Fixed = "",
[string]$SevenZip = "C:\Program Files\7-Zip\7z.exe",
[string]$SfxModule = "C:\Program Files\7-Zip\7z.sfx",
[string]$OutputDir = "."
)
$ErrorActionPreference = "Stop"
if ($Version -notin @("v3", "v4")) {
throw "Unsupported Version '$Version'. Use v3 or v4."
}
if (!(Test-Path $BinPath)) {
throw "Missing binary: $BinPath"
}
$Root = Resolve-Path (Join-Path $PSScriptRoot "..\..")
$OutRoot = Resolve-Path $OutputDir
$Work = Join-Path $PSScriptRoot "payload_$Version"
$Archive = Join-Path $PSScriptRoot "payload_$Version.7z"
$ZipOut = Join-Path $OutRoot "Avida-ED-$Version-Windows-x64.zip"
$ExeOut = Join-Path $OutRoot "Avida-ED-$Version-Windows-x64.exe"
Remove-Item $Work -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item $Archive -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path $Work | Out-Null
Copy-Item $BinPath -Destination (Join-Path $Work "avidaed_onefile.exe")
$HasFixedRuntime = $false
if ($WV2Fixed -and (Test-Path $WV2Fixed)) {
Copy-Item $WV2Fixed -Destination (Join-Path $Work "WebView2Fixed") -Recurse
$HasFixedRuntime = $true
}
$runbat = @"
@echo off
setlocal
set HERE=%~dp0
if exist "%HERE%WebView2Fixed\msedgewebview2.exe" set WEBVIEW2_BROWSER_EXECUTABLE_FOLDER=%HERE%WebView2Fixed
start "" "%HERE%avidaed_onefile.exe"
"@
$runbat | Out-File -Encoding ASCII (Join-Path $Work "run.bat")
$readme = @"
Avida-ED $Version for Windows
Run run.bat to start Avida-ED.
This package includes a WebView2 Fixed Runtime: $HasFixedRuntime
If no fixed runtime is bundled, Microsoft Edge WebView2 Runtime must already be installed.
"@
$readme | Out-File -Encoding UTF8 (Join-Path $Work "README.txt")
if (!(Test-Path $SevenZip)) {
throw "Missing 7-Zip at $SevenZip"
}
& $SevenZip a -tzip $ZipOut "$Work\*" | Write-Host
Write-Host "Wrote $ZipOut"
if (Test-Path $SfxModule) {
& $SevenZip a -t7z $Archive "$Work\*" | Write-Host
$Cfg = Join-Path $PSScriptRoot "config.txt"
$OutStream = [System.IO.File]::Create($ExeOut)
try {
foreach ($Path in @($SfxModule, $Cfg, $Archive)) {
$Bytes = [System.IO.File]::ReadAllBytes($Path)
$OutStream.Write($Bytes, 0, $Bytes.Length)
}
} finally {
$OutStream.Close()
}
Write-Host "Wrote $ExeOut"
} else {
Write-Warning "SFX module not found at $SfxModule; skipped EXE creation."
}

2697
server-ui/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,14 @@ edition = "2021"
[dependencies]
wry = "0.39" # WebView wrapper: WebView2/WKWebView/WebKitGTK
tao = "0.28" # Window/event loop used by wry
tiny_http = "0.12" # tiny static server
tiny-http = "0.12" # tiny static server
include_dir = "0.7" # embed webroot into binary
mime_guess = "2.0"
once_cell = "1.19"
anyhow = "1.0"
[profile.release]
lto = true
codegen-units = 1
strip = "symbols"

View File

@ -1,34 +1,3 @@
fn main() {
use std::{fs, path::Path};
let webroot = Path::new("webroot");
if !webroot.exists() {
fs::create_dir_all(webroot).expect("create fallback webroot");
fs::write(
webroot.join("index.html"),
"<!doctype html><title>Avida-ED assets not injected</title><p>Run make fetch-v4 build-linux.</p>\n",
)
.expect("write fallback webroot index");
}
println!("cargo:rerun-if-changed=webroot");
print_rerun_files(webroot);
println!("cargo:rerun-if-env-changed=AVIDA_ED_DEFAULT_PATH");
let default_path = std::env::var("AVIDA_ED_DEFAULT_PATH")
.unwrap_or_else(|_| "/index.html".to_string());
println!("cargo:rustc-env=AVIDA_ED_DEFAULT_PATH={default_path}");
}
fn print_rerun_files(path: &std::path::Path) {
let Ok(entries) = std::fs::read_dir(path) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
print_rerun_files(&path);
} else {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}

View File

@ -1,16 +1,16 @@
use include_dir::{include_dir, Dir};
use tiny_http::{Request, Response, Method};
use tao::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
use wry::{
application::event::{Event, StartCause, WindowEvent},
application::event_loop::{ControlFlow, EventLoop},
application::window::WindowBuilder,
webview::WebViewBuilder
};
use wry::{PageLoadEvent, WebViewBuilder};
use std::{net::TcpListener, time::Duration};
use std::{net::TcpListener, thread, time::Duration};
use anyhow::Result;
static WEBROOT: Dir = include_dir!("$CARGO_MANIFEST_DIR/webroot");
static DEFAULT_PATH: &str = env!("AVIDA_ED_DEFAULT_PATH");
static DEFAULT_PATH: &str = "/Avida-ED-Eco/index.html";
fn mime(p: &str)->&'static str{
if p.ends_with(".wasm"){return "application/wasm";}
@ -19,7 +19,7 @@ fn mime(p: &str)->&'static str{
if p.ends_with(".html") || p.ends_with(".htm"){return "text/html; charset=utf-8";}
mime_guess::from_path(p).first_raw().unwrap_or("application/octet-stream")
}
fn serve(req:Request){
fn serve(mut req:Request){
if req.method()!=&Method::Get && req.method()!=&Method::Head{
let _=req.respond(Response::from_string("Method Not Allowed").with_status_code(405));return;
}
@ -28,21 +28,17 @@ fn serve(req:Request){
if path=="/"{path=DEFAULT_PATH.to_string();}
let fpath=path.trim_start_matches('/');
if let Some(f)=WEBROOT.get_file(fpath){
eprintln!("Avida-ED HTTP 200 {path}");
let mut resp=Response::from_data(f.contents().to_vec());
let _=resp.add_header(tiny_http::Header::from_bytes(b"Content-Type", mime(&path)).unwrap());
let _=resp.add_header(tiny_http::Header::from_bytes(b"Cache-Control", b"no-store").unwrap());
let _=req.respond(resp);
}else{
eprintln!("Avida-ED HTTP 404 {path}");
let _=req.respond(Response::from_string("Not Found").with_status_code(404));
}
}
fn main()->Result<()>{
let listener=TcpListener::bind(("127.0.0.1",0))?;
let port=listener.local_addr()?.port();
let srv=tiny_http::Server::from_listener(listener, None)
.map_err(|e| anyhow::anyhow!("failed to create HTTP server: {e}"))?;
let listener=TcpListener::bind(("127.0.0.1",0))?; listener.set_nonblocking(true)?;
let port=listener.local_addr()?.port(); let srv=tiny_http::Server::from_tcp(listener)?;
std::thread::spawn(move||{
loop{
match srv.recv_timeout(Duration::from_millis(200)){
@ -51,71 +47,17 @@ fn main()->Result<()>{
}
});
let url=format!("http://127.0.0.1:{port}{DEFAULT_PATH}");
let event_loop=EventLoop::new();
let event_loop=EventLoop::new()?;
let window=WindowBuilder::new()
.with_title("Avida-ED")
.with_inner_size(tao::dpi::LogicalSize::new(1280.0,800.0))
.with_inner_size(wry::application::dpi::LogicalSize::new(1280.0,800.0))
.build(&event_loop)?;
eprintln!("Avida-ED loading {url}");
#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
))]
let builder = WebViewBuilder::new(&window);
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
)))]
let builder = {
use tao::platform::unix::WindowExtUnix;
use wry::WebViewBuilderExtUnix;
let vbox = window.default_vbox().ok_or_else(|| anyhow::anyhow!("failed to get GTK window container"))?;
WebViewBuilder::new_gtk(vbox)
};
let _wv=builder
.with_initialization_script(
r#"
(() => {
const send = (message) => {
try {
if (window.ipc && window.ipc.postMessage) window.ipc.postMessage(String(message));
} catch (_) {}
};
window.addEventListener('error', (event) => {
send(`js-error:${event.message} at ${event.filename}:${event.lineno}:${event.colno}`);
});
window.addEventListener('unhandledrejection', (event) => {
send(`js-rejection:${event.reason}`);
});
console.error = new Proxy(console.error, {
apply(target, thisArg, args) {
send(`console-error:${args.map(String).join(' ')}`);
return Reflect.apply(target, thisArg, args);
}
});
})();
"#,
)
.with_ipc_handler(|req| eprintln!("Avida-ED WebView {}", req.body()))
.with_on_page_load_handler(|event, url| {
let event_name = match event {
PageLoadEvent::Started => "started",
PageLoadEvent::Finished => "finished",
};
eprintln!("Avida-ED page-load {event_name} {url}");
})
.with_url(&url)
.build()?;
let _wv=WebViewBuilder::new(&window)?.with_url(&url)?.build()?;
event_loop.run(move|e,_,cf|{
*cf=ControlFlow::Wait;
if let Event::WindowEvent{event:WindowEvent::CloseRequested,..}=e{*cf=ControlFlow::Exit;}
*cf=wry::application::event_loop::ControlFlow::Wait;
if let Event::WindowEvent{event:WindowEvent::CloseRequested,..}=e{*cf=wry::application::event_loop::ControlFlow::Exit;}
if let Event::NewEvents(StartCause::Init)=e{}
});
})?;
Ok(())
}

View File

@ -1,79 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: tests/smoke_appimage.sh PATH_TO_APPIMAGE [SECONDS]
Runs an Avida-ED AppImage long enough to verify wrapper startup, embedded HTTP
serving, WebView page load, and absence of proxied JavaScript errors.
The app normally keeps running until its window closes, so timeout exit code 124
is accepted after the expected load signals appear.
USAGE
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
appimage="${1:?missing AppImage path}"
seconds="${2:-20}"
if [[ ! -x "$appimage" ]]; then
echo "error: AppImage is missing or not executable: $appimage" >&2
exit 2
fi
tmpdir="$(mktemp -d)"
logfile="$tmpdir/appimage.log"
cleanup() {
rm -rf "$tmpdir"
}
trap cleanup EXIT
cmd=(
env
LIBGL_ALWAYS_SOFTWARE="${LIBGL_ALWAYS_SOFTWARE:-1}"
WEBKIT_DISABLE_COMPOSITING_MODE="${WEBKIT_DISABLE_COMPOSITING_MODE:-1}"
"$appimage"
)
if [[ -z "${DISPLAY:-}" ]] && command -v xvfb-run >/dev/null 2>&1; then
cmd=(xvfb-run -a "${cmd[@]}")
fi
set +e
timeout "$seconds" "${cmd[@]}" >"$logfile" 2>&1
status=$?
set -e
cat "$logfile"
if [[ "$status" -ne 0 && "$status" -ne 124 ]]; then
echo "error: AppImage exited before smoke window completed; status=$status" >&2
exit "$status"
fi
if ! grep -q "Avida-ED loading http://127.0.0.1:" "$logfile"; then
echo "error: wrapper did not log local HTTP load URL" >&2
exit 1
fi
if ! grep -q "Avida-ED HTTP 200" "$logfile"; then
echo "error: embedded HTTP server did not serve any successful response" >&2
exit 1
fi
if ! grep -q "Avida-ED page-load finished" "$logfile"; then
echo "error: WebView did not finish loading the app page" >&2
exit 1
fi
if grep -E "Avida-ED WebView (js-error|js-rejection|console-error):" "$logfile"; then
echo "error: WebView reported JavaScript errors" >&2
exit 1
fi
echo "AppImage smoke passed: $appimage"

View File

@ -2,5 +2,4 @@ FROM alpine:3.20
RUN apk add --no-cache ca-certificates curl bash rsync
WORKDIR /work
COPY fetch_assets.sh /work/fetch_assets.sh
RUN chmod +x /work/fetch_assets.sh
ENTRYPOINT ["/work/fetch_assets.sh"]

View File

@ -1,60 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
MODE="${MODE:-github}" # github|docker|url
MODE="${MODE:-docker}" # docker|url
ED_VER="${ED_VER:-v4}" # v3|v4
OUTDIR="${OUTDIR:-/out}"
case "$ED_VER" in
v3) APP_DIR="Avida-ED"; GITHUB_REPO="https://github.com/Avida-ED/Avida-ED3.git" ;;
v4) APP_DIR="Avida-ED-Eco"; GITHUB_REPO="https://github.com/Avida-ED/Avida-ED4.git" ;;
*) echo "Unknown ED_VER=$ED_VER" >&2; exit 2 ;;
esac
mkdir -p "$OUTDIR"
case "$MODE" in
github)
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
git clone --depth 1 "$GITHUB_REPO" "$TMPDIR/source"
rm -rf "$OUTDIR/$APP_DIR"
mkdir -p "$OUTDIR/$APP_DIR"
if command -v rsync >/dev/null 2>&1; then
rsync -a --exclude .git "$TMPDIR/source"/ "$OUTDIR/$APP_DIR"/
else
(cd "$TMPDIR/source" && tar --exclude .git -cf - .) | (cd "$OUTDIR/$APP_DIR" && tar -xf -)
fi
;;
docker)
# 1) Start your aed-docker container and copy its served webroot
# Expectation: container serves Avida-ED-Eco at /usr/share/nginx/html/Avida-ED-Eco
if ! CID="$(docker create --name aedtmp_$ED_VER welsberr/aed-docker:$ED_VER)"; then
echo "Could not create welsberr/aed-docker:$ED_VER. The canonical source is GitHub; try make fetch-$ED_VER." >&2
exit 1
fi
CID="$(docker create --name aedtmp_$ED_VER welsberr/aed-docker:$ED_VER)"
trap 'docker rm -f aedtmp_'"$ED_VER"' >/dev/null 2>&1 || true' EXIT
docker cp "aedtmp_$ED_VER:/usr/share/nginx/html/." "$OUTDIR/"
;;
url)
URL="${URL:-https://avida-ed.msu.edu/app4/}"
apk add --no-cache wget >/dev/null 2>&1 || true
WGET_STATUS=0
wget --recursive --no-parent --page-requisites --adjust-extension \
--compression=auto --convert-links --timestamping \
--directory-prefix "$OUTDIR" \
"$URL" || WGET_STATUS=$?
# normalize into the directory shape expected by the Rust injection step
if [ ! -d "$OUTDIR/$APP_DIR" ]; then
"$URL"
# normalize into $OUTDIR/Avida-ED-Eco as needed
if [ ! -d "$OUTDIR/Avida-ED-Eco" ]; then
SUB="$(find "$OUTDIR" -type f -name index.html | head -n1)"
if [ -n "$SUB" ]; then
if command -v rsync >/dev/null 2>&1; then
rsync -a "$(dirname "$SUB")"/ "$OUTDIR/$APP_DIR"/
else
cp -R "$(dirname "$SUB")"/. "$OUTDIR/$APP_DIR"/
fi
fi
fi
if [ ! -f "$OUTDIR/$APP_DIR/index.html" ]; then
echo "URL fetch failed: no normalized $OUTDIR/$APP_DIR/index.html found. wget exit status: $WGET_STATUS" >&2
exit "${WGET_STATUS:-1}"
[ -n "$SUB" ] && rsync -a "$(dirname "$SUB")"/ "$OUTDIR/Avida-ED-Eco"/
fi
;;
*) echo "Unknown MODE=$MODE" >&2; exit 1;;

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
VER="${1:-${VER:-v4}}"
case "$VER" in
v3) APP_DIR="Avida-ED" ;;
v4) APP_DIR="Avida-ED-Eco" ;;
*) echo "Unsupported VER=$VER. Use v3 or v4." >&2; exit 2 ;;
esac
SRC="apps/$VER/$APP_DIR"
DEST="server-ui/webroot/$APP_DIR"
if [ ! -f "$SRC/index.html" ]; then
echo "Missing $SRC/index.html. Run make fetch-$VER first." >&2
exit 2
fi
rm -rf server-ui/webroot
mkdir -p server-ui/webroot
if command -v rsync >/dev/null 2>&1; then
rsync -a "$SRC/" "$DEST/"
else
mkdir -p "$DEST"
cp -R "$SRC"/. "$DEST"/
fi
echo "Injected $SRC into $DEST"