diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml new file mode 100644 index 0000000..b547c34 --- /dev/null +++ b/.forgejo/workflows/check.yml @@ -0,0 +1,24 @@ +name: check + +# Fast-fail lint/test on short-lived work branches, before it ever reaches +# main and triggers a dev-track release build. +on: + push: + branches: ['feature/**', 'fix/**'] + +jobs: + check: + runs-on: [self-hosted, hestia] + steps: + - name: checkout + run: | + set -euo pipefail + rm -rf src && mkdir src + git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ + "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src + + - name: clippy + run: cd src && bash ci/build.sh cargo clippy --workspace --all-targets --locked -- -D warnings + + - name: test + run: cd src && bash ci/build.sh cargo test --workspace --locked diff --git a/.forgejo/workflows/dev-release.yml b/.forgejo/workflows/dev-release.yml index cf92ac0..11f75d2 100644 --- a/.forgejo/workflows/dev-release.yml +++ b/.forgejo/workflows/dev-release.yml @@ -15,27 +15,8 @@ jobs: git clone --branch main --depth 1 \ "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - name: build inside container - run: | - docker run --rm \ - -v "$(pwd)/src:/workspace" \ - -w /workspace \ - fedora:41 \ - bash -c " - set -euo pipefail - dnf install -y gcc gcc-c++ pkgconf-pkg-config git curl meson ninja-build valac gtk4-devel graphene-devel - - # Build Libadwaita 1.7.0 from source inside container (skipping tests & docs) - git clone https://gitlab.gnome.org/GNOME/libadwaita.git --branch 1.7.0 --depth 1 /tmp/libadwaita - meson setup /tmp/libadwaita/build /tmp/libadwaita --prefix=/usr -Dtests=false -Ddoc=false - ninja -C /tmp/libadwaita/build - ninja -C /tmp/libadwaita/build install - - # Install Rust and build - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - source \$HOME/.cargo/env - cargo build --release --locked - " + - name: build + run: cd src && bash ci/build.sh cargo build --release --locked - name: compute dev version run: | @@ -83,4 +64,4 @@ jobs: ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" git clone --branch main https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" TRACK=dev bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" - rm -rf "${ECOSYSTEM_CI_DIR}" \ No newline at end of file + rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/rc-release.yml b/.forgejo/workflows/rc-release.yml index 493710e..aad2f82 100644 --- a/.forgejo/workflows/rc-release.yml +++ b/.forgejo/workflows/rc-release.yml @@ -2,11 +2,10 @@ name: beta (rc) release on: push: - tags: ['v*'] + tags: ['v*-rc.*'] jobs: build: - if: ${{ contains(github.ref_name, '-rc.') }} runs-on: [self-hosted, hestia] steps: - name: checkout @@ -16,27 +15,8 @@ jobs: git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - name: build inside container - run: | - docker run --rm \ - -v "$(pwd)/src:/workspace" \ - -w /workspace \ - fedora:41 \ - bash -c " - set -euo pipefail - dnf install -y gcc gcc-c++ pkgconf-pkg-config git curl meson ninja-build valac gtk4-devel graphene-devel - - # Build Libadwaita 1.7.0 from source inside container (skipping tests & docs) - git clone https://gitlab.gnome.org/GNOME/libadwaita.git --branch 1.7.0 --depth 1 /tmp/libadwaita - meson setup /tmp/libadwaita/build /tmp/libadwaita --prefix=/usr -Dtests=false -Ddoc=false - ninja -C /tmp/libadwaita/build - ninja -C /tmp/libadwaita/build install - - # Install Rust and build - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - source \$HOME/.cargo/env - cargo build --release --locked - " + - name: build + run: cd src && bash ci/build.sh cargo build --release --locked - name: prepare artifacts run: | @@ -68,4 +48,4 @@ jobs: ECOSYSTEM_CI_DIR="$(mktemp -d /tmp/bread-ecosystem-ci-XXXXXX)" git clone https://git.breadway.dev/Breadway/bread-ecosystem.git "${ECOSYSTEM_CI_DIR}" TRACK=beta bash "${ECOSYSTEM_CI_DIR}/scripts/gen-index.sh" - rm -rf "${ECOSYSTEM_CI_DIR}" \ No newline at end of file + rm -rf "${ECOSYSTEM_CI_DIR}" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 91a05c9..d21777c 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -2,11 +2,11 @@ name: release on: push: - tags: ["v*"] + tags: ['v*'] + tags-ignore: ['v*-rc.*'] jobs: build: - if: ${{ !contains(github.ref_name, '-rc.') }} runs-on: [self-hosted, hestia] steps: - name: checkout @@ -16,27 +16,8 @@ jobs: git clone --branch "${GITHUB_REF_NAME}" --depth 1 \ "https://git.breadway.dev/${GITHUB_REPOSITORY}.git" src - - name: build inside container - run: | - docker run --rm \ - -v "$(pwd)/src:/workspace" \ - -w /workspace \ - fedora:41 \ - bash -c " - set -euo pipefail - dnf install -y gcc gcc-c++ pkgconf-pkg-config git curl meson ninja-build valac gtk4-devel graphene-devel - - # Build Libadwaita 1.7.0 from source inside container (skipping tests & docs) - git clone https://gitlab.gnome.org/GNOME/libadwaita.git --branch 1.7.0 --depth 1 /tmp/libadwaita - meson setup /tmp/libadwaita/build /tmp/libadwaita --prefix=/usr -Dtests=false -Ddoc=false - ninja -C /tmp/libadwaita/build - ninja -C /tmp/libadwaita/build install - - # Install Rust and build - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - source \$HOME/.cargo/env - cargo build --release --locked - " + - name: build + run: cd src && bash ci/build.sh cargo build --release --locked - name: prepare artifacts run: | @@ -76,4 +57,4 @@ jobs: "${PKG_DIR}/breadman-x86_64" \ "${PKG_DIR}/breadpad-x86_64.sha256" \ "${PKG_DIR}/breadman-x86_64.sha256" \ - --clobber \ No newline at end of file + --clobber diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 607f493..93077a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,13 +69,21 @@ cargo test --release --workspace ## CI +- `check.yml` — clippy + test, triggered on push to `feature/**`/`fix/**`. + Fast-fail before anything reaches `main`. - `dev-release.yml` — triggered on push to `main`. - `rc-release.yml` — triggered on any `vX.Y.Z-rc.N` tag push. - `release.yml` — triggered on any other `v*` tag push, cuts the actual stable release. -All CI runs on a self-hosted runner; nothing runs automatically on plain -commits or PRs beyond the track builds above. See +All of these build inside a pinned Arch Linux container (`ci/Containerfile`, +run via `ci/build.sh`) on a self-hosted runner — not the runner host's +native environment. Arch's repos carry current `gtk4`/`libadwaita`/ +`gtk4-layer-shell` as prebuilt packages, so there's no from-source library +build to go stale. The image is rebuilt (and re-cached by Docker) only when +`ci/Containerfile` changes, so a plain push doesn't refetch or recompile +the toolchain. Nothing runs automatically on plain commits or PRs beyond +the jobs listed above. See [bread-ecosystem's docs/release-channels.md](https://git.breadway.dev/Breadway/bread-ecosystem/src/branch/main/docs/release-channels.md) for the full policy, including how a new product gets wired onto these tracks. diff --git a/breadman/src/main.rs b/breadman/src/main.rs index 7e07a4e..48430d7 100644 --- a/breadman/src/main.rs +++ b/breadman/src/main.rs @@ -108,13 +108,15 @@ mod args { // ── AppState ────────────────────────────────────────────────────────────────── +type ErrorLog = Rc, String)>>>; + /// Shared UI state, cheap to clone (all fields are Rc/Arc). #[derive(Clone)] struct AppState { store: Arc, notes: Rc>>, cfg: Rc>, - errors: Rc, String)>>>, + errors: ErrorLog, active_view: Rc>, stack: gtk4::Stack, window: gtk4::ApplicationWindow, @@ -579,7 +581,7 @@ fn build_note_list( .build(); let mut sorted: Vec = notes.iter().filter(|n| !n.done).cloned().collect(); - sorted.sort_by(|a, b| b.created.cmp(&a.created)); + sorted.sort_by_key(|n| std::cmp::Reverse(n.created)); if sorted.is_empty() { let action = empty_new_type.map(|nt| views::row::new_note_action(nt, state.window.clone(), state.clone())); diff --git a/breadman/src/views/errors.rs b/breadman/src/views/errors.rs index 1be64e0..cedb9ff 100644 --- a/breadman/src/views/errors.rs +++ b/breadman/src/views/errors.rs @@ -35,7 +35,7 @@ pub fn build(entries: &[(DateTime, String)]) -> gtk4::ScrolledWin .build(); let time_label = gtk4::Label::builder() - .label(&ts.format("%H:%M:%S").to_string()) + .label(ts.format("%H:%M:%S").to_string()) .width_chars(10) .xalign(0.0) .css_classes(["dim-label"]) diff --git a/breadpad-shared/src/ai.rs b/breadpad-shared/src/ai.rs index 7bb69ad..24f2bd4 100644 --- a/breadpad-shared/src/ai.rs +++ b/breadpad-shared/src/ai.rs @@ -73,7 +73,7 @@ impl OllamaClient { let classification: OllamaClassification = extract_json(&ollama_resp.response) .ok_or_else(|| anyhow::anyhow!( "no JSON object found in response — raw: {:?}", - &ollama_resp.response + ollama_resp.response ))?; let note_type = classification diff --git a/breadpad-shared/src/config.rs b/breadpad-shared/src/config.rs index 9d81e5e..e0f9bcf 100644 --- a/breadpad-shared/src/config.rs +++ b/breadpad-shared/src/config.rs @@ -135,7 +135,7 @@ impl Default for RemindersConfig { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct CalendarConfig { #[serde(default = "default_calendar_enabled")] pub enabled: bool, @@ -150,17 +150,6 @@ pub struct CalendarConfig { pub password: String, } -impl Default for CalendarConfig { - fn default() -> Self { - CalendarConfig { - enabled: false, - url: String::new(), - username: String::new(), - password: String::new(), - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Config { #[serde(default)] diff --git a/breadpad-shared/src/parser.rs b/breadpad-shared/src/parser.rs index cc03b5e..27f63b9 100644 --- a/breadpad-shared/src/parser.rs +++ b/breadpad-shared/src/parser.rs @@ -324,6 +324,67 @@ pub fn parse_rule_based(text: &str, default_morning: &str) -> ClassificationResu } } +fn infer_type(text: &str, has_time: bool, has_rrule: bool) -> NoteType { + let lower = text.to_lowercase(); + if has_rrule || has_time { + return NoteType::Reminder; + } + if lower.contains("buy ") + || lower.contains("pick up") + || lower.contains("clean ") + || lower.starts_with("call ") + || lower.starts_with("email ") + || lower.starts_with("fix ") + || lower.starts_with("check ") + || lower.starts_with("finish ") + || lower.starts_with("write ") + || lower.starts_with("update ") + || lower.starts_with("prepare ") + || lower.starts_with("schedule ") + || lower.starts_with("organize ") + || lower.starts_with("deploy ") + || lower.starts_with("install ") + || lower.starts_with("send ") + || lower.starts_with("submit ") + || lower.starts_with("create ") + || lower.starts_with("setup ") + || lower.starts_with("restore ") + || lower.starts_with("archive ") + || lower.starts_with("export ") + || lower.starts_with("import ") + || lower.starts_with("approve ") + || lower.starts_with("configure ") + || lower.starts_with("refactor ") + || lower.starts_with("review ") + { + return NoteType::Todo; + } + if lower.starts_with("what if ") + || lower.starts_with("idea:") + || lower.contains("could ") + || lower.contains("maybe ") + || lower.starts_with("should we ") + { + return NoteType::Idea; + } + if lower.starts_with("why ") + || lower.starts_with("how ") + || (lower.starts_with("what ") && !lower.starts_with("what if ")) + || lower.starts_with("when ") + || lower.starts_with("where ") + || lower.starts_with("who ") + || lower.starts_with("will ") + || lower.starts_with("is ") + || lower.starts_with("are ") + || lower.starts_with("did ") + || lower.starts_with("does ") + || lower.ends_with('?') + { + return NoteType::Question; + } + NoteType::Note +} + #[cfg(test)] mod tests { use super::*; @@ -490,7 +551,7 @@ mod tests { let r = p("take a break in 30 minutes"); let t = r.time.unwrap(); let delta = (t - before).num_seconds(); - assert!(delta >= 29 * 60 && delta <= 31 * 60, "delta was {}s", delta); + assert!((29 * 60..=31 * 60).contains(&delta), "delta was {}s", delta); } #[test] @@ -498,7 +559,7 @@ mod tests { let before = Utc::now(); let r = p("ping in 1 minute"); let delta = (r.time.unwrap() - before).num_seconds(); - assert!(delta >= 55 && delta <= 65, "delta was {}s", delta); + assert!((55..=65).contains(&delta), "delta was {}s", delta); } #[test] @@ -506,7 +567,7 @@ mod tests { let before = Utc::now(); let r = p("review PR in 2 hours"); let delta_min = (r.time.unwrap() - before).num_minutes(); - assert!(delta_min >= 119 && delta_min <= 121, "delta was {}min", delta_min); + assert!((119..=121).contains(&delta_min), "delta was {}min", delta_min); } #[test] @@ -514,7 +575,7 @@ mod tests { let before = Utc::now(); let r = p("follow up in 3 days"); let delta_h = (r.time.unwrap() - before).num_hours(); - assert!(delta_h >= 71 && delta_h <= 73, "delta was {}h", delta_h); + assert!((71..=73).contains(&delta_h), "delta was {}h", delta_h); } // ---- Time extraction: tomorrow ---- @@ -717,7 +778,7 @@ mod tests { let before = Utc::now(); let r = p("check on the server in an hour"); let delta_min = (r.time.unwrap() - before).num_minutes(); - assert!(delta_min >= 59 && delta_min <= 61, "delta was {}min", delta_min); + assert!((59..=61).contains(&delta_min), "delta was {}min", delta_min); } #[test] @@ -737,7 +798,7 @@ mod tests { let before = Utc::now(); let r = p("in a couple of hours remind me to check the oven"); let delta_min = (r.time.unwrap() - before).num_minutes(); - assert!(delta_min >= 119 && delta_min <= 121, "delta was {}min", delta_min); + assert!((119..=121).contains(&delta_min), "delta was {}min", delta_min); } #[test] @@ -758,7 +819,7 @@ mod tests { let before = Utc::now(); let r = p("in half an hour submit the report"); let delta_min = (r.time.unwrap() - before).num_minutes(); - assert!(delta_min >= 29 && delta_min <= 31, "delta was {}min", delta_min); + assert!((29..=31).contains(&delta_min), "delta was {}min", delta_min); } // ---- Tonight / this evening ---- @@ -847,64 +908,3 @@ mod tests { assert!(rule.as_str().contains("BYHOUR=16"), "rule: {}", rule.as_str()); } } - -fn infer_type(text: &str, has_time: bool, has_rrule: bool) -> NoteType { - let lower = text.to_lowercase(); - if has_rrule || has_time { - return NoteType::Reminder; - } - if lower.contains("buy ") - || lower.contains("pick up") - || lower.contains("clean ") - || lower.starts_with("call ") - || lower.starts_with("email ") - || lower.starts_with("fix ") - || lower.starts_with("check ") - || lower.starts_with("finish ") - || lower.starts_with("write ") - || lower.starts_with("update ") - || lower.starts_with("prepare ") - || lower.starts_with("schedule ") - || lower.starts_with("organize ") - || lower.starts_with("deploy ") - || lower.starts_with("install ") - || lower.starts_with("send ") - || lower.starts_with("submit ") - || lower.starts_with("create ") - || lower.starts_with("setup ") - || lower.starts_with("restore ") - || lower.starts_with("archive ") - || lower.starts_with("export ") - || lower.starts_with("import ") - || lower.starts_with("approve ") - || lower.starts_with("configure ") - || lower.starts_with("refactor ") - || lower.starts_with("review ") - { - return NoteType::Todo; - } - if lower.starts_with("what if ") - || lower.starts_with("idea:") - || lower.contains("could ") - || lower.contains("maybe ") - || lower.starts_with("should we ") - { - return NoteType::Idea; - } - if lower.starts_with("why ") - || lower.starts_with("how ") - || (lower.starts_with("what ") && !lower.starts_with("what if ")) - || lower.starts_with("when ") - || lower.starts_with("where ") - || lower.starts_with("who ") - || lower.starts_with("will ") - || lower.starts_with("is ") - || lower.starts_with("are ") - || lower.starts_with("did ") - || lower.starts_with("does ") - || lower.ends_with('?') - { - return NoteType::Question; - } - NoteType::Note -} diff --git a/breadpad-shared/src/scheduler.rs b/breadpad-shared/src/scheduler.rs index ff5ff26..580e247 100644 --- a/breadpad-shared/src/scheduler.rs +++ b/breadpad-shared/src/scheduler.rs @@ -176,7 +176,7 @@ pub(crate) fn parse_next_from_rrule(rrule_str: &str, default_morning: &str) -> O } else { (now.date_naive() + chrono::Duration::days(1)).and_time(fire_time) }; - return Some(local_naive_to_utc(naive)); + Some(local_naive_to_utc(naive)) } "WEEKLY" => { use chrono::Datelike; @@ -204,7 +204,7 @@ pub(crate) fn parse_next_from_rrule(rrule_str: &str, default_morning: &str) -> O }; let target_date = (now.date_naive() + chrono::Duration::days(days_ahead)).and_time(fire_time); - return Some(local_naive_to_utc(target_date)); + Some(local_naive_to_utc(target_date)) } _ => None, } diff --git a/breadpad-shared/src/store.rs b/breadpad-shared/src/store.rs index bc8b86a..a8f52c8 100644 --- a/breadpad-shared/src/store.rs +++ b/breadpad-shared/src/store.rs @@ -118,11 +118,11 @@ impl Store { Ok(()) } - fn rewrite_notes(&self, mut f: F) -> Result<()> + fn rewrite_notes(&self, f: F) -> Result<()> where F: FnMut(Note) -> Note, { - let notes: Vec = self.load_all()?.into_iter().map(|n| f(n)).collect(); + let notes: Vec = self.load_all()?.into_iter().map(f).collect(); self.write_all(&self.notes_path, ¬es) } @@ -145,7 +145,7 @@ impl Store { let notes = self.load_all()?; let (to_archive, keep): (Vec, Vec) = notes .into_iter() - .partition(|n| n.done && n.completed.map_or(false, |c| c < cutoff)); + .partition(|n| n.done && n.completed.is_some_and(|c| c < cutoff)); if to_archive.is_empty() { return Ok(0); diff --git a/breadpad-shared/src/theme.rs b/breadpad-shared/src/theme.rs index e6fa63e..670b711 100644 --- a/breadpad-shared/src/theme.rs +++ b/breadpad-shared/src/theme.rs @@ -278,7 +278,7 @@ mod tests { #[test] fn css_defines_bg_color() { let css = build_css(&Palette::default(), None); - assert!(css.contains("@define-color bg #1e1e2e"), "css missing bg: {}", &css[..300]); + assert!(css.contains("@define-color bg #0c0c0c"), "css missing bg: {}", &css[..300]); } #[test] @@ -323,9 +323,11 @@ mod tests { #[test] fn css_reflects_custom_palette_colors() { - let mut p = Palette::default(); - p.background = "#deadbe".into(); - p.color4 = "#cafe00".into(); + let p = Palette { + background: "#deadbe".into(), + color4: "#cafe00".into(), + ..Default::default() + }; let css = build_css(&p, None); assert!(css.contains("@define-color bg #deadbe"), "css: {}", &css[..300]); assert!(css.contains("@define-color blue #cafe00"), "css: {}", &css[..300]); diff --git a/breadpad-shared/src/types.rs b/breadpad-shared/src/types.rs index 5be0b54..e1d5729 100644 --- a/breadpad-shared/src/types.rs +++ b/breadpad-shared/src/types.rs @@ -15,6 +15,9 @@ pub enum NoteType { } impl NoteType { + // Not std::str::FromStr — infallible, returns Self directly rather than + // Result, and used across 30+ call sites as NoteType::from_str(..). + #[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "todo" => NoteType::Todo, diff --git a/breadpad-shared/tests/config.rs b/breadpad-shared/tests/config.rs index 6e057e5..462afe6 100644 --- a/breadpad-shared/tests/config.rs +++ b/breadpad-shared/tests/config.rs @@ -279,15 +279,19 @@ fn resolved_ort_dylib_empty_returns_none() { #[test] fn resolved_ort_dylib_whitespace_only_returns_none() { - let mut m = ModelConfig::default(); - m.ort_dylib_path = " ".into(); + let m = ModelConfig { + ort_dylib_path: " ".into(), + ..Default::default() + }; assert!(m.resolved_ort_dylib_path().is_none()); } #[test] fn resolved_ort_dylib_set_returns_some() { - let mut m = ModelConfig::default(); - m.ort_dylib_path = "/usr/lib/libonnxruntime.so".into(); + let m = ModelConfig { + ort_dylib_path: "/usr/lib/libonnxruntime.so".into(), + ..Default::default() + }; assert_eq!( m.resolved_ort_dylib_path().unwrap().to_str().unwrap(), "/usr/lib/libonnxruntime.so" diff --git a/breadpad-test/src/main.rs b/breadpad-test/src/main.rs index 54a7d03..4c96a43 100644 --- a/breadpad-test/src/main.rs +++ b/breadpad-test/src/main.rs @@ -389,7 +389,7 @@ fn cmd_show(index: usize, corpus_path: &Path, tier: &TierArg) -> Result<()> { println!(); let sep = "─".repeat(62); - println!("{:<14} {:<26} {}", "field", "expected", "actual"); + println!("{:<14} {:<26} actual", "field", "expected"); println!("{sep}"); println!( "{:<14} {:<26} {}", diff --git a/breadpad/src/main.rs b/breadpad/src/main.rs index 4949963..b284cb8 100644 --- a/breadpad/src/main.rs +++ b/breadpad/src/main.rs @@ -468,7 +468,7 @@ fn build_reminder_window( let local: chrono::DateTime = t.into(); header.append( >k4::Label::builder() - .label(&local.format("%H:%M").to_string()) + .label(local.format("%H:%M").to_string()) .css_classes(["reminder-time"]) .build(), ); diff --git a/ci/Containerfile b/ci/Containerfile new file mode 100644 index 0000000..45b0ba4 --- /dev/null +++ b/ci/Containerfile @@ -0,0 +1,22 @@ +# Pinned CI build environment for breadpad. +# +# Arch instead of Fedora because breadpad targets BOS/Arch only, and Arch's +# repos already carry current libadwaita/gtk4/gtk4-layer-shell as prebuilt +# packages — no from-source libadwaita build needed (that from-source build +# was the repeated CI breakage: rust dep mismatches, libadwaita ABI +# mismatches, and finally a removed meson option). +# +# Base image pinned by digest, package set frozen at build time: this image +# only changes when someone deliberately rebuilds it, not on every push. +FROM archlinux@sha256:fae033b815a16f930325c2697e620362be4d2e5d739a301b10ad1fc9c8643a06 + +RUN pacman -Syu --noconfirm --needed \ + base-devel \ + git \ + pkgconf \ + rust \ + gtk4 \ + libadwaita \ + gtk4-layer-shell \ + graphene \ + && pacman -Scc --noconfirm diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 0000000..fbaac36 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Builds (or reuses, via docker's own layer cache) the pinned Arch CI image +# from ci/Containerfile, then runs the given cargo command inside it against +# this repo checkout. +# +# Cargo's registry/git caches and CARGO_TARGET_DIR are persisted in named +# docker volumes so they survive across runs even though the repo checkout +# itself (a fresh --depth 1 clone per workflow run) does not. +# +# Usage: ci/build.sh cargo build --release --locked +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +docker build -t breadpad-ci:archlinux -f ci/Containerfile ci + +docker run --rm \ + -v "${ROOT}:/workspace" \ + -v breadpad-cargo-registry:/root/.cargo/registry \ + -v breadpad-cargo-git:/root/.cargo/git \ + -v breadpad-cargo-target:/cargo-target \ + -w /workspace \ + -e CARGO_TARGET_DIR=/cargo-target \ + breadpad-ci:archlinux \ + bash -c ' + set -euo pipefail + "$@" + mkdir -p /workspace/target + cp -a /cargo-target/. /workspace/target/ + ' bash "$@"