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
30 changes: 28 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,19 @@ jobs:
os: windows-latest
name_suffix: " (windows)"
cache_suffix: "-windows"
# Restoring a 3.9 GB `target/` cache costs Windows more than
# compiling from a warm sccache does, so this job caches only
# `~/.cargo` and lets sccache carry compilation.
cache_targets: "false"
# Windows pays a steep per-file cost to unpack, compile and link, so
# PR runs cover only the crates that make up `jp` itself. The Linux
# `test` job still builds and tests everything on every PR, and
# `main` covers the full workspace on Windows too.
scope: ${{ github.ref == 'refs/heads/main' && 'workspace' || 'jp-only' }}
name: ${{ matrix.task }}${{ matrix.name_suffix }}
# A run without a target cache falls back to a full build. Cap it so a
# pathological run doesn't burn an hour of Windows minutes unnoticed.
timeout-minutes: 60
steps:
- name: Decide whether to run task
id: task
Expand Down Expand Up @@ -150,7 +162,11 @@ jobs:
with:
path: |
~/.rustup
key: toolchain-${{ matrix.task }}${{ matrix.cache_suffix }}
key: toolchain-${{ matrix.task }}${{ matrix.cache_suffix }}-${{ hashFiles('rust-toolchain.toml') }}
# Prefix match, so a toolchain bump falls back to the previous entry
# instead of paying for a full rustup download.
restore-keys: |
toolchain-${{ matrix.task }}${{ matrix.cache_suffix }}
- if: ${{ steps.task.outputs.run == 'true' }}
uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3
- if: ${{ steps.task.outputs.run == 'true' }}
Expand All @@ -159,6 +175,7 @@ jobs:
key: ${{ matrix.task }}
cache-bin: false
cache-workspace-crates: true
cache-targets: ${{ matrix.cache_targets || 'true' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- if: ${{ steps.task.outputs.run == 'true' && matrix.task != 'coverage' && matrix.task != 'shear' }}
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # V0.10
Expand All @@ -171,8 +188,17 @@ jobs:
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV"
echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV"
# `rust-cache` strips `~/.cargo/registry/src` before saving, so the first
# cargo invocation of the job re-extracts every `.crate` archive. That is
# the single most expensive non-build phase on Windows. Doing it in a
# named step keeps the cost visible instead of burying it in the silent
# `cargo metadata` that `cargo nextest run` shells out to on startup.
- name: Warm the registry source tree
if: ${{ steps.task.outputs.run == 'true' && matrix.task == 'test' }}
shell: bash
run: cargo metadata --locked --format-version 1 > /dev/null
- if: ${{ steps.task.outputs.run == 'true' }}
run: just ${{ matrix.task }}-ci
run: just ${{ matrix.task }}-ci ${{ matrix.scope }}
- if: ${{ matrix.task == 'coverage' && steps.task.outputs.run == 'true' }}
uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2
with:
Expand Down
25 changes: 25 additions & 0 deletions crates/jp_tombmap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
//! A hash map that remembers what changed.
//!
//! [`TombMap`] wraps a [`HashMap`] and records which keys were removed (leaving
//! a tombstone behind) and which were inserted or mutated.
//! A persistence layer can use those two sets to write only the entries that
//! changed and delete the ones that went away, instead of rewriting the whole
//! map.

use std::{
borrow::Borrow,
collections::{HashMap, HashSet, TryReserveError, hash_map},
Expand All @@ -12,6 +20,23 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};

use self::Entry::*;

/// A [`HashMap`] that tracks removals and modifications.
///
/// Removing a key records a tombstone, so [`removed_keys`] still reports it
/// afterwards.
/// Inserting a key or handing out a mutable reference to its value marks the
/// key as modified.
/// The `_untracked` methods (such as [`remove_untracked`] and
/// [`iter_mut_untracked`]) skip that bookkeeping.
///
/// Only the live entries are serialized.
/// Deserializing therefore yields a map with no tombstones and nothing marked
/// modified, so both sets describe the changes made since the map was last
/// read.
///
/// [`iter_mut_untracked`]: TombMap::iter_mut_untracked
/// [`remove_untracked`]: TombMap::remove_untracked
/// [`removed_keys`]: TombMap::removed_keys
pub struct TombMap<K, V, S = RandomState> {
live: HashMap<K, V, S>,
dead: HashSet<K>,
Expand Down
16 changes: 14 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ vet_version := "0.10.2"

quiet_flag := if env_var_or_default("CI", "") == "true" { "" } else { "--quiet" }

# Workspace crates that no `jp_*` crate depends on: standalone tooling binaries,
# docs infrastructure, and command plugins. Skipping them drops their
# dependencies from the build entirely, rather than merely skipping their tests.
#
# `grizzly` is deliberately absent: `jp_attachment_bear_note` depends on it, so
# it is compiled either way.
non_jp_excludes := "--exclude bookworm --exclude build-registry --exclude comfort --exclude jp-path --exclude jp-serve-web --exclude json_edit --exclude tools"

alias r := run
alias i := install
alias c := check
Expand Down Expand Up @@ -2082,9 +2090,13 @@ fmt-markdown-ci: _install-comfort _install_ci_matchers
comfort --check --workspace --language markdown --format-markdown --reference-links --prune-reference-links

# Test the code on CI.
#
# `SCOPE` selects the crates to test: `workspace` covers every member, `jp-only`
# skips the crates listed in `non_jp_excludes`. An unrecognised scope tests the
# full workspace.
[group('ci')]
test-ci: (_install "cargo-nextest@" + nextest_version) _install_ci_matchers
cargo nextest run --locked --lib --tests --cargo-profile=nextest --status-level=slow --failure-output=immediate-final --workspace --no-fail-fast
test-ci SCOPE="workspace": (_install "cargo-nextest@" + nextest_version) _install_ci_matchers
cargo nextest run --locked --lib --tests --cargo-profile=nextest --status-level=slow --failure-output=immediate-final --workspace --no-fail-fast {{ if SCOPE == "jp-only" { non_jp_excludes } else { "" } }}

# Generate documentation on CI.
[group('ci')]
Expand Down
Loading