UI.9: keep browser bundle under size budget on current main

This commit is contained in:
skylar taylor-barrick committed 2026-10-10 20:56:34 -06:00
commit d0a18cfd83
11 files changed
+827 -311

No files matched your search

+71 -1
View File
@@ -372,9 +372,79 @@ jobs:
path: dist/release/*
if-no-files-found: error
linux-riscv64:
name: Linux riscv64
needs: version
# Cross-compiled on x86_64 (no hosted RISC-V runners) against the runner's own Ubuntu release
# from ports.ubuntu.com, so the binaries need that release's glibc (24.04: 2.39). 22.04's
# riscv64 ports lag its amd64 updates, which breaks the multiarch install. tar.gz only; the
# CLI is smoke-tested under QEMU. Same recipe as GridCraft's linux-riscv64 job.
runs-on: ubuntu-24.04
# No `environment: release`: this job reads no secrets, so a branch dry run can run it.
env:
PHOTOCRAFT_VERSION: ${{ needs.version.outputs.version }}
PHOTOCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
- name: Check out craft-fonts (embedded fonts)
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: storytold/craft-fonts
ref: ${{ env.CRAFT_FONTS_REF }}
path: craft-fonts
- uses: dtolnay/rust-toolchain@stable
with:
targets: riscv64gc-unknown-linux-gnu
- uses: Swatinem/rust-cache@v2
with:
key: release-linux-riscv64
- name: Apply version override
if: inputs.version != ''
run: cargo xtask version set "$PHOTOCRAFT_VERSION"
- name: Install cross-compilation dependencies
run: |
. /etc/os-release
sudo dpkg --add-architecture riscv64
# Pin the existing (archive.ubuntu.com) sources to amd64; riscv64 comes from ports.
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then
sudo sed -i '/^Types: deb/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources
fi
if [ -f /etc/apt/sources.list ]; then
sudo sed -i 's/^deb /deb [arch=amd64] /g' /etc/apt/sources.list
sudo sed -i 's/^deb-src /deb-src [arch=amd64] /g' /etc/apt/sources.list
fi
for suite in "$VERSION_CODENAME" "$VERSION_CODENAME-updates" "$VERSION_CODENAME-security"; do
echo "deb [arch=riscv64] http://ports.ubuntu.com/ubuntu-ports $suite main universe restricted multiverse"
done | sudo tee /etc/apt/sources.list.d/riscv64.list
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu libc6-dev-riscv64-cross libc6-dev:riscv64 qemu-user \
libxkbcommon-dev:riscv64 libwayland-dev:riscv64 libx11-dev:riscv64 libxrandr-dev:riscv64 \
libxi-dev:riscv64 libgl1-mesa-dev:riscv64 \
desktop-file-utils appstream
- name: Build and package (tar.gz)
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
CRAFT_FONTS_REQUIRED: "1"
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
PKG_CONFIG_SYSROOT_DIR: /
PKG_CONFIG_PATH: /usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig
PKG_CONFIG_ALLOW_CROSS: "1"
CROSS_ARCH: riscv64
CROSS_TARGET: riscv64gc-unknown-linux-gnu
CROSS_COMPILE: riscv64-linux-gnu-
EMULATOR: qemu-riscv64
QEMU_LD_PREFIX: /usr/riscv64-linux-gnu
run: packaging/linux/package.sh --formats tar
- uses: actions/upload-artifact@v4
with:
name: release-linux-riscv64
path: dist/release/*.tar.gz
if-no-files-found: error
release:
name: Draft GitHub Release
needs: [version, macos, windows, linux, flatpak, freebsd, web]
needs: [version, macos, windows, linux, linux-riscv64, flatpak, freebsd, web]
runs-on: ubuntu-latest
environment: release
permissions:
Generated
+29 -29
View File
@@ -4182,7 +4182,7 @@ dependencies = [
[[package]]
name = "photocraft"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"arboard",
"async-io",
@@ -4223,7 +4223,7 @@ dependencies = [
[[package]]
name = "photocraft-affinity"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"crc32fast",
"flate2",
@@ -4233,7 +4233,7 @@ dependencies = [
[[package]]
name = "photocraft-algo"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-geom",
@@ -4246,7 +4246,7 @@ dependencies = [
[[package]]
name = "photocraft-automation"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"base64",
"cap-std",
@@ -4268,7 +4268,7 @@ dependencies = [
[[package]]
name = "photocraft-cli"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-automation",
"photocraft-codecs",
@@ -4282,7 +4282,7 @@ dependencies = [
[[package]]
name = "photocraft-cms"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"moxcms",
"rayon",
@@ -4290,7 +4290,7 @@ dependencies = [
[[package]]
name = "photocraft-codecs"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"exr",
"flate2",
@@ -4311,7 +4311,7 @@ dependencies = [
[[package]]
name = "photocraft-color"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-cms",
"serde",
@@ -4319,7 +4319,7 @@ dependencies = [
[[package]]
name = "photocraft-compose"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"criterion",
"photocraft-color",
@@ -4334,7 +4334,7 @@ dependencies = [
[[package]]
name = "photocraft-doc"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-geom",
@@ -4345,7 +4345,7 @@ dependencies = [
[[package]]
name = "photocraft-engine"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"blake3",
"exr",
@@ -4378,7 +4378,7 @@ dependencies = [
[[package]]
name = "photocraft-format"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"blake3",
"flate2",
@@ -4397,14 +4397,14 @@ dependencies = [
[[package]]
name = "photocraft-geom"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"serde",
]
[[package]]
name = "photocraft-gpu"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"half",
"log",
@@ -4421,14 +4421,14 @@ dependencies = [
[[package]]
name = "photocraft-heif"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"heic-rs",
]
[[package]]
name = "photocraft-io"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"blake3",
"crc32fast",
@@ -4457,14 +4457,14 @@ dependencies = [
[[package]]
name = "photocraft-ops"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-doc",
]
[[package]]
name = "photocraft-paint"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-geom",
@@ -4475,7 +4475,7 @@ dependencies = [
[[package]]
name = "photocraft-plugins"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-geom",
@@ -4490,7 +4490,7 @@ dependencies = [
[[package]]
name = "photocraft-psd"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"flate2",
"photocraft-psd",
@@ -4501,7 +4501,7 @@ dependencies = [
[[package]]
name = "photocraft-raster"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-geom",
@@ -4511,7 +4511,7 @@ dependencies = [
[[package]]
name = "photocraft-raw"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"flate2",
"image",
@@ -4526,7 +4526,7 @@ dependencies = [
[[package]]
name = "photocraft-tablet"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"block2 0.6.2",
"objc2 0.6.4",
@@ -4538,7 +4538,7 @@ dependencies = [
[[package]]
name = "photocraft-testkit"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"memory-stats",
"photocraft-color",
@@ -4549,7 +4549,7 @@ dependencies = [
[[package]]
name = "photocraft-text"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"flate2",
"parley",
@@ -4563,7 +4563,7 @@ dependencies = [
[[package]]
name = "photocraft-ui-egui"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"base64",
"craft-ui",
@@ -4601,7 +4601,7 @@ dependencies = [
[[package]]
name = "photocraft-vector"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"photocraft-color",
"photocraft-doc",
@@ -4612,7 +4612,7 @@ dependencies = [
[[package]]
name = "photocraft-web"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"eframe",
"egui",
@@ -7445,7 +7445,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "xtask"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"serde",
"serde_json",
+9 -5
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/photocraft", "apps/photocraft-cli", "xtask"]
[workspace.package]
version = "0.6.0"
version = "0.7.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -81,11 +81,10 @@ codegen-units = 1
# The browser build (apps/photocraft-web, `data-cargo-profile` in its index.html). Hosts cap the
# size of a single file (Cloudflare Pages/Workers: 25 MiB), and the wasm is downloaded on every
# visit, so this profile optimizes for size: fat LTO, and opt-level "s" for everything except the
# pixel-processing crates, which keep opt-level 3 so filters and compositing stay fast. panic
# visit, so this profile optimizes for size: fat LTO, opt-level "z" for the UI shell, and "s" for
# other crates except pixel processing, which keeps opt-level 3 for filters and compositing. panic
# stays the target default (wasm32-unknown-unknown aborts; AGENTS.md's `panic = "unwind"` is for
# the native app shell). packaging/web/package.sh fails the build above 24 MiB. Issue #198:
# this took the wasm from 26.3 MiB (release profile) to 18.8 MiB.
# the native app shell). packaging/web/package.sh fails the build above 24 MiB.
[profile.wasm-release]
inherits = "release"
opt-level = "s"
@@ -96,6 +95,11 @@ strip = "debuginfo"
[profile.wasm-release.package."*"]
opt-level = "s"
# Keep the browser shell compact as shared panel controls grow. Pixel-processing crates below
# retain their speed-oriented optimization level.
[profile.wasm-release.package.photocraft-ui-egui]
opt-level = "z"
[profile.wasm-release.package.photocraft-raster]
opt-level = 3
[profile.wasm-release.package.photocraft-color]
+2
View File
@@ -350,6 +350,8 @@ Installers and executables are code-signed.
| Fedora/RHEL/openSUSE | `photocraft-<ver>-linux-x86_64.rpm` | `photocraft-<ver>-linux-aarch64.rpm` | |
| Tarball | `photocraft-<ver>-linux-x86_64.tar.gz` | `photocraft-<ver>-linux-aarch64.tar.gz` | Unpack anywhere |
RISC-V (riscv64): `photocraft-<ver>-linux-riscv64.tar.gz` only, built against Ubuntu 24.04 (needs glibc 2.39+).
### FreeBSD
| Build | File |
+7 -1
View File
@@ -227,7 +227,13 @@ pub fn missing_message(session: DisplaySession, missing: &[AnyOf], distro: Distr
/// Library directories to search: `LD_LIBRARY_PATH`, then the usual system and Flatpak paths.
fn search_dirs(ld_library_path: Option<&str>) -> Vec<PathBuf> {
let mut dirs: Vec<PathBuf> = ld_library_path.unwrap_or("").split(':').filter(|d| !d.is_empty()).map(PathBuf::from).collect();
let triplet = if cfg!(target_arch = "aarch64") { "aarch64-linux-gnu" } else { "x86_64-linux-gnu" };
let triplet = if cfg!(target_arch = "aarch64") {
"aarch64-linux-gnu"
} else if cfg!(target_arch = "riscv64") {
"riscv64-linux-gnu"
} else {
"x86_64-linux-gnu"
};
for d in ["/app/lib", "/usr/local/lib", "/usr/lib64", "/lib64", "/usr/lib", "/lib"] {
dirs.push(PathBuf::from(d));
}
File diff suppressed because it is too large. Load diff
+9
View File
@@ -265,6 +265,15 @@ pub(crate) fn marquee_ants(xf: &ViewXform, tool: Tool, a: [f64; 2], b: [f64; 2])
if tool != Tool::EllipseMarquee {
return vec![at(x0, y0), at(x1, y0), at(x1, y1), at(x0, y1)];
}
// An axis-aligned view (rotation a multiple of 90 degrees) maps the ellipse onto the one
// inscribed in the snapped screen box of two opposite corners. Draw that, unsnapped: snapping
// each vertex on its own turns the nearly flat arcs at the edge midpoints into 1 px stairs,
// whose antialiased steps wash the ants out to grey on white and black.
let quarter = xf.rotation.rem_euclid(90.0);
if quarter < 1e-4 || 90.0 - quarter < 1e-4 {
let r = Rect::from_two_pos(at(x0, y0), at(x1, y1));
return crate::tool_feedback::ellipse_points(r);
}
// As many vertices as the same ellipse would get drawn on screen (`ellipse_points`).
let scr = Rect::from_points(&[xf.to_screen(x0 as f32, y0 as f32), xf.to_screen(x1 as f32, y1 as f32)]);
let n = (((scr.width() + scr.height()) * 0.25) as usize).clamp(16, 256);
+7 -64
View File
@@ -128,7 +128,7 @@ fn coverage(img: &Image, ppp: f32, a: Pos2, b: Pos2, test: impl Fn(u8) -> bool)
let len = a.distance(b).max(1.0);
let dir = (b - a) / len;
let normal = egui::vec2(-dir.y, dir.x);
// Skip rectangle corners, where other edges and the readout may be.
// Skip the ends (corners, where the other edges and the readout may be).
let n = (len as usize).saturating_sub(16);
let mut hits = 0;
for i in 0..n {
@@ -142,31 +142,6 @@ fn coverage(img: &Image, ppp: f32, a: Pos2, b: Pos2, test: impl Fn(u8) -> bool)
hits as f32 / n.max(1) as f32
}
/// Sample eight dash periods along the actual ellipse, including its curvature.
/// A short tangent segment can land on antialiasing ramps instead of the stroke's core.
fn ellipse_coverage(images: &[&Image], ppp: f32, center: Pos2, radius: egui::Vec2, midpoint: Pos2, test: impl Fn(u8) -> bool) -> f32 {
let (center, radius, midpoint) = (center * ppp, radius * ppp, midpoint * ppp);
let horizontal = (midpoint.y - center.y).abs() > (midpoint.x - center.x).abs();
let mut hits = 0;
for offset in -32..32 {
let p = if horizontal {
let x = midpoint.x + offset as f32;
let y = center.y + (midpoint.y - center.y).signum() * radius.y * (1.0 - ((x - center.x) / radius.x).powi(2)).max(0.0).sqrt();
egui::pos2(x, y)
} else {
let y = midpoint.y + offset as f32;
let x = center.x + (midpoint.x - center.x).signum() * radius.x * (1.0 - ((y - center.y) / radius.y).powi(2)).max(0.0).sqrt();
egui::pos2(x, y)
};
let normal = if horizontal { egui::vec2(0.0, 1.0) } else { egui::vec2(1.0, 0.0) };
hits += usize::from((-2..=2).any(|o| {
let q = p + normal * o as f32;
images.iter().any(|img| img.luma(q.x.round() as i64, q.y.round() as i64).is_some_and(&test))
}));
}
hits as f32 / 64.0
}
/// Ink that contrasts with `background`: dark on white, light on black.
fn ink(background: &str) -> impl Fn(u8) -> bool {
let white = background == "white";
@@ -190,17 +165,8 @@ fn marquee_previews_show_while_dragging_on_white_and_black_at_1x_and_2x() {
setup(&mut h, bg, tool);
let (a, b) = ([100.0, 80.0], [300.0, 220.0]);
let before = render(&mut h);
let before_time = h.ctx.input(|input| input.time) + 0.4;
h.input_mut().time = Some(before_time);
h.step();
let before_next = render(&mut h);
drag_to(&mut h, a, b);
let img = render(&mut h);
if let Some(directory) = std::env::var_os("PHOTOCRAFT_DRAG_PREVIEW_FIXTURES") {
let directory = std::path::PathBuf::from(directory);
std::fs::create_dir_all(&directory).unwrap();
h.render().expect("capture render").save(directory.join(format!("{tool}-{bg}-{ppp}x.png"))).unwrap();
}
if tool == "rectMarquee" {
for (i, (p, q)) in edges(&h, a, b).into_iter().enumerate() {
let was = coverage(&before, ppp, p, q, ink(bg));
@@ -209,46 +175,23 @@ fn marquee_previews_show_while_dragging_on_white_and_black_at_1x_and_2x() {
assert!(was < 0.05 && now > 0.3, "{tool} on {bg} @{ppp}x, edge {i}: {was:.2} → {now:.2} contrasting");
}
} else {
// Sample both halves of the animated dash cycle while the pointer is held.
// A single phase can put antialiasing ramps at a short arc's sample positions.
let next_time = h.ctx.input(|input| input.time) + 0.4;
h.input_mut().time = Some(next_time);
h.step();
let next = render(&mut h);
if let Some(directory) = std::env::var_os("PHOTOCRAFT_DRAG_PREVIEW_FIXTURES") {
h.render().expect("capture next phase").save(std::path::PathBuf::from(directory).join(format!("{tool}-{bg}-{ppp}x-next.png"))).unwrap();
}
// Check actual arcs around all four ellipse extrema at every display scale.
let center = screen(&h, 200.0, 150.0);
let radius = egui::vec2((screen(&h, 300.0, 150.0).x - center.x).abs(), (screen(&h, 200.0, 220.0).y - center.y).abs());
// The ellipse touches its box at the edge midpoints: check short arcs there.
// The arcs keep a physical length at any scale, so their sagitta stays inside
// `coverage`'s ±2 physical px search.
for (mx, my) in [(200.0, 80.0), (300.0, 150.0), (200.0, 220.0), (100.0, 150.0)] {
let m = screen(&h, mx, my);
let was = ellipse_coverage(&[&before, &before_next], ppp, center, radius, m, ink(bg));
let now = ellipse_coverage(&[&img, &next], ppp, center, radius, m, ink(bg));
assert!(was < 0.05 && now > 0.3, "{tool} on {bg} @{ppp}x at {m:?}: {was:.2} → {now:.2} contrasting");
let along = if mx == 200.0 { egui::vec2(14.0 / ppp, 0.0) } else { egui::vec2(0.0, 14.0 / ppp) };
let now = coverage(&img, ppp, m - along, m + along, ink(bg));
assert!(now > 0.3, "{tool} on {bg} @{ppp}x at {m:?}: {now:.2} contrasting");
}
}
// Each move's frame shows the outline where the pointer is now (no stale frame).
let previous = img;
pointer_move(&mut h, 340.0, 250.0);
let img = render(&mut h);
if tool == "rectMarquee" {
let [top, right, ..] = edges(&h, a, [340.0, 250.0]);
assert!(coverage(&img, ppp, top.0, top.1, ink(bg)) > 0.3, "{tool} {bg} @{ppp}x: top edge follows the pointer");
assert!(coverage(&img, ppp, right.0, right.1, ink(bg)) > 0.3, "{tool} {bg} @{ppp}x: right edge follows the pointer");
} else {
let next_time = h.ctx.input(|input| input.time) + 0.4;
h.input_mut().time = Some(next_time);
h.step();
let next = render(&mut h);
assert_ne!(img.px, next.px, "held ellipse must animate between the sampled phases");
let center = screen(&h, 220.0, 165.0);
let radius = egui::vec2((screen(&h, 340.0, 165.0).x - center.x).abs(), (screen(&h, 220.0, 250.0).y - center.y).abs());
for midpoint in [screen(&h, 340.0, 165.0), screen(&h, 220.0, 250.0)] {
let old = ellipse_coverage(&[&previous], ppp, center, radius, midpoint, ink(bg));
let now = ellipse_coverage(&[&img, &next], ppp, center, radius, midpoint, ink(bg));
assert!(old < 0.05 && now > 0.3, "ellipse {bg} @{ppp}x follows current bounds: {old:.2} → {now:.2}");
}
}
button(&mut h, 340.0, 250.0, false);
h.run_steps(2);
+1 -1
View File
@@ -351,7 +351,7 @@ trunk build --release # writes ../../dist/web (index.html, .js glue
trunk serve --release # dev server on http://127.0.0.1:8765
```
Any static file server works for `dist/web`, for example `python3 -m http.server 8765` run inside that directory. Trunk downloads the matching `wasm-bindgen` and `wasm-opt` itself. `trunk build --release` uses the `wasm-release` Cargo profile (`data-cargo-profile` in `index.html`: fat LTO, opt-level "s" except the pixel crates). The `.wasm` is about 18.8 MiB raw, 7.8 MiB gzipped and 5.6 MiB with Brotli; serve it with compression. Keep it under 24 MiB (`packaging/web/package.sh` enforces this; Cloudflare's per-file cap is 25 MiB). Pull-request CI also builds the actual browser app with the same pinned Trunk version as the release job and runs `packaging/web/package.sh`, including the relative-URL and 24 MiB checks. The `web build and wasm size gate` check runs on main and on PRs that change something the web build compiles or packages (`apps/photocraft-web`, `packaging/web`, crate sources and manifests, `Cargo.toml`/`Cargo.lock`, the CI workflow), and reports the resulting wasm size in the Actions job summary. The web build never embeds craft-fonts (see Fonts above). To see where the bytes go, run `twiggy top -n 40` on `target/wasm32-unknown-unknown/wasm-release/photocraft-web.wasm` (before wasm-opt strips the names).
Any static file server works for `dist/web`, for example `python3 -m http.server 8765` run inside that directory. Trunk downloads the matching `wasm-bindgen` and `wasm-opt` itself. `trunk build --release` uses the `wasm-release` Cargo profile (`data-cargo-profile` in `index.html`: fat LTO, opt-level "z" for `photocraft-ui-egui`, "s" for other crates except pixel processing, which keeps level 3). The UI override reduces browser code size while retaining the speed-oriented settings for filters and compositing. Serve the `.wasm` with compression; its raw size varies with the source and compiler. Keep it under 24 MiB (`packaging/web/package.sh` enforces this; Cloudflare's per-file cap is 25 MiB). Pull-request CI also builds the actual browser app with the same pinned Trunk version as the release job and runs `packaging/web/package.sh`, including the relative-URL and 24 MiB checks. The `web build and wasm size gate` check runs on main and on PRs that change something the web build compiles or packages (`apps/photocraft-web`, `packaging/web`, crate sources and manifests, `Cargo.toml`/`Cargo.lock`, the CI workflow), and reports the resulting wasm size in the Actions job summary. The web build never embeds craft-fonts (see Fonts above). To see where the bytes go, run `twiggy top -n 40` on `target/wasm32-unknown-unknown/wasm-release/photocraft-web.wasm` (before wasm-opt strips the names).
URL flags: `?webgl` forces the WebGL2 backend, and `?cpu` forces the CPU canvas path.
+1
View File
@@ -53,6 +53,7 @@ in the dialog.
| Windows 10+ x86 (32-bit) | `photocraft-<v>-windows-x86.msi`, `photocraft-<v>-windows-x86-portable.zip` | `windows-latest` |
| Linux x86_64 | `photocraft-<v>-linux-x86_64.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}` | `ubuntu-22.04` (Flatpak: `ubuntu-24.04`) |
| Linux aarch64 | `photocraft-<v>-linux-aarch64.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}` | `ubuntu-22.04-arm` (Flatpak: `ubuntu-24.04-arm`) |
| Linux riscv64 (glibc 2.39+) | `photocraft-<v>-linux-riscv64.tar.gz` | cross-compiled on `ubuntu-24.04` (ports.ubuntu.com riscv64 libs); CLI smoke-tested under QEMU |
| FreeBSD 14 x86_64 | `photocraft-<v>-freebsd-x86_64.tar.gz` | FreeBSD 14.3 VM on `ubuntu-latest` |
| Web | `photocraft-web-<v>.zip` (static site; see [`packaging/web/README.md`](../packaging/web/README.md)) | `ubuntu-latest` |
+20 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Build and package PhotoCraft for Linux (<arch> is x86_64 or aarch64):
# Build and package PhotoCraft for Linux (<arch> is x86_64, aarch64 or riscv64):
#
# $DIST/photocraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
# $DIST/photocraft-<version>-linux-<arch>.AppImage.zsync delta updates (needs zsyncmake)
@@ -13,6 +13,10 @@
# (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04,
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli,
# zsyncmake (the zsync package) for the AppImage's .zsync.
#
# Cross builds (CI: riscv64, tar.gz only): set CROSS_ARCH (riscv64), CROSS_TARGET (the Rust
# triple), CROSS_COMPILE (binutils prefix, for strip) and optionally EMULATOR (e.g. qemu-riscv64)
# to smoke-test the CLI.
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
@@ -25,13 +29,14 @@ while [ $# -gt 0 ]; do
case "$1" in
--skip-build) SKIP_BUILD=1; shift ;;
--formats) FORMATS="$2"; shift 2 ;;
-h | --help) sed -n '2,15p' "$0"; exit 0 ;;
-h | --help) sed -n '2,19p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
ARCH="$(uname -m)"
ARCH="${CROSS_ARCH:-$(uname -m)}"
case "$ARCH" in
riscv64) DEB_ARCH=riscv64 ;;
x86_64) DEB_ARCH=amd64 ;;
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
@@ -42,9 +47,13 @@ BASENAME="photocraft-$VERSION-linux-$ARCH"
echo "==> PhotoCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
(cd "$ROOT" && cargo build --release --locked -p photocraft -p photocraft-cli --features heif)
if [ -n "${CROSS_TARGET:-}" ]; then
(cd "$ROOT" && cargo build --release --locked -p photocraft -p photocraft-cli --features heif --target "$CROSS_TARGET")
else
(cd "$ROOT" && cargo build --release --locked -p photocraft -p photocraft-cli --features heif)
fi
fi
BIN="$CARGO_TARGET_DIR/release"
BIN="$CARGO_TARGET_DIR/${CROSS_TARGET:+$CROSS_TARGET/}release"
WORK="$CARGO_TARGET_DIR/linux-package"
STAGE="$WORK/root"
rm -rf "$WORK"
@@ -52,7 +61,7 @@ rm -rf "$WORK"
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
install -Dm755 "$BIN/photocraft" "$STAGE/usr/bin/photocraft"
install -Dm755 "$BIN/photocraft-cli" "$STAGE/usr/bin/photocraft-cli"
strip "$STAGE/usr/bin/photocraft" "$STAGE/usr/bin/photocraft-cli" 2>/dev/null || true
"${CROSS_COMPILE:-}strip" "$STAGE/usr/bin/photocraft" "$STAGE/usr/bin/photocraft-cli" 2>/dev/null || true
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
mkdir -p "$STAGE/usr/share/metainfo"
@@ -139,6 +148,10 @@ if has appimage; then
fi
fi
"$STAGE/usr/bin/photocraft-cli" --version
if [ -z "${CROSS_TARGET:-}" ]; then
"$STAGE/usr/bin/photocraft-cli" --version
elif [ -n "${EMULATOR:-}" ]; then
"$EMULATOR" "$STAGE/usr/bin/photocraft-cli" --version
fi
echo "==> done"
ls -lh "$DIST"