mirror of
https://github.com/atuinsh/atuin.git
synced 2026-10-11 22:29:51 +00:00
ci: run CI on Buildkite (#4274)
Move atuin's CI over to Buildkite. I've removed a few jobs from branches and pushed them onto main, mostly things like the nixos build, illumos etc - they were really slow, and difficult to optimise. The goal is to be on buildkit, and have CI take around a minute per commit. Once we have more tests, we can keep parallelizing I tested out both the nvme disks and the buildkite cache. The former is much faster, but is trickier to isolate across branches. I would prefer that there is 0 chance of a branch messing with a build on main, or with another branch. --------- Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d2ddf8c0f9
commit
c7f0318eb3
@@ -0,0 +1,88 @@
|
||||
# Buildkite Cache definitions, used by `buildkite-agent cache restore/save`
|
||||
# in .buildkite/pipeline.yml.
|
||||
#
|
||||
# Keys go coarse to fine. The toolchain file is the last required part
|
||||
# (fallback_limit), so a Cargo.lock change restores the newest entry for the
|
||||
# same toolchain and cargo rebuilds only the dependencies that changed.
|
||||
#
|
||||
# Branch isolation comes from the cluster's cache registry policy: entries
|
||||
# are saved scoped to their branch, and restores search the current branch
|
||||
# and then main, so a branch build can never write into what main restores.
|
||||
#
|
||||
# Bump `v1` to invalidate a cache; an entry is written once per address.
|
||||
|
||||
caches:
|
||||
# The crates.io index and downloaded .crate files. Identical for every
|
||||
# step, so only the check step saves it.
|
||||
- name: cargo_registry
|
||||
cache_key:
|
||||
- cargo-registry
|
||||
- v1
|
||||
- agent: os
|
||||
- agent: arch
|
||||
fallback_limit: true
|
||||
- checksum: Cargo.lock
|
||||
target_paths:
|
||||
- ~/.cargo/registry/index
|
||||
- ~/.cargo/registry/cache
|
||||
|
||||
# target/ after prune-target.sh: third-party dependency builds only. One
|
||||
# entry per step, since each step builds a different profile and feature
|
||||
# set (the key's `step` part is the step key).
|
||||
- name: target
|
||||
cache_key:
|
||||
- target
|
||||
# v2: dev/test builds without debuginfo (pipeline env).
|
||||
- v2
|
||||
- agent: step
|
||||
- agent: os
|
||||
- agent: arch
|
||||
- checksum: rust-toolchain.toml
|
||||
fallback_limit: true
|
||||
- checksum: Cargo.lock
|
||||
target_paths:
|
||||
- target
|
||||
|
||||
# The step's Rust toolchain (rustup's dir plus ~/.cargo/bin, where rustup
|
||||
# and its proxies live): restoring it takes a second or two against ~11s
|
||||
# to download. Per step, since steps pin different toolchains (MSRV) and
|
||||
# components (clippy). Linux only: macOS images ship several toolchains,
|
||||
# so there the entry is 1.7 GB and slower to restore than installing. Not
|
||||
# used for the nightly rustfmt step, which should pick up new nightlies.
|
||||
- name: rustup
|
||||
cache_key:
|
||||
- rustup
|
||||
- v1
|
||||
- agent: step
|
||||
- agent: os
|
||||
- agent: arch
|
||||
- checksum: rust-toolchain.toml
|
||||
target_paths:
|
||||
- ~/.rustup
|
||||
- ~/.cargo/bin
|
||||
|
||||
# .debs apt_install downloaded (libssl-dev, pkg-config, and per-step extras
|
||||
# like zsh/fish), installed with dpkg on later jobs. Per step because the
|
||||
# package lists differ. Bump v1 if the hosted image's base packages move
|
||||
# on (dpkg --refuse-downgrade falls back to apt meanwhile).
|
||||
- name: apt_debs
|
||||
cache_key:
|
||||
- apt-debs
|
||||
- v1
|
||||
- agent: step
|
||||
- agent: os
|
||||
- agent: arch
|
||||
target_paths:
|
||||
- ~/.cache/apt-debs
|
||||
|
||||
# The OmniOS VM image anyvm downloads for the illumos step. Keyed on the
|
||||
# script, which pins the anyvm, OmniOS and builder versions.
|
||||
- name: anyvm
|
||||
cache_key:
|
||||
- anyvm
|
||||
- v1
|
||||
- agent: os
|
||||
- agent: arch
|
||||
- checksum: .buildkite/scripts/illumos.sh
|
||||
target_paths:
|
||||
- ~/.cache/anyvm
|
||||
@@ -0,0 +1,516 @@
|
||||
# atuin CI on Buildkite: PROTOTYPE, running alongside GitHub Actions to
|
||||
# compare. Ports rust.yml (Linux + macOS; Windows and the illumos VM job
|
||||
# stay on GitHub Actions for now), nix.yml, codespell.yml, shellcheck.yml,
|
||||
# vale.yml, installer.yml and nightly.yml, with the same jobs and commands.
|
||||
#
|
||||
# Each step sources .buildkite/scripts/toolchain.sh (rustup + pinned tools,
|
||||
# and the `section` log-group helper). Caching is Buildkite Cache
|
||||
# (.buildkite/cache.yml): the cargo registry, plus a per-step target/ pruned
|
||||
# to third-party dependency builds (prune-target.sh), keyed on the toolchain
|
||||
# and Cargo.lock. Saves run only after the step's commands have succeeded.
|
||||
#
|
||||
# Queues: light jobs use the cluster's default queue (linux-small, 2 vCPU).
|
||||
# Compile-bound jobs use linux-large (8 vCPU) and macOS jobs macos-large
|
||||
# (12 vCPU), both default queues in every Buildkite cluster. Test steps are
|
||||
# split across jobs with `parallelism` + nextest --partition.
|
||||
#
|
||||
# Not ported yet: the `paths-ignore: ui/**` filters on rust.yml and nix.yml
|
||||
# (if_changed needs a diff base set for main builds first).
|
||||
#
|
||||
# Scheduled builds (the nightly schedule, set in the pipeline settings) run
|
||||
# everything plus the nightly tests, which also keeps main's cache entries
|
||||
# from expiring.
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
# No debuginfo in dev/test builds: a big share of their compile and link
|
||||
# time, unused in CI. debug_assertions stay on; panics still report
|
||||
# file:line, backtraces just lose line numbers.
|
||||
CARGO_PROFILE_DEV_DEBUG: "0"
|
||||
CARGO_PROFILE_TEST_DEBUG: "0"
|
||||
# .config/nextest.toml's ci profile: writes test-results/junit.xml, which
|
||||
# the test-collector plugin uploads to Buildkite Test Engine.
|
||||
NEXTEST_PROFILE: ci
|
||||
|
||||
steps:
|
||||
- label: ":rust: Build (release)"
|
||||
key: build
|
||||
# main only: PRs are type-checked in the release profile by `check`
|
||||
# (cargo check --release), and nearly all of this job is LLVM codegen
|
||||
# for the atuin binary (~2 min on any machine).
|
||||
if: build.branch == pipeline.default_branch
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/build"
|
||||
agents:
|
||||
queue: linux-large
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
# Only proves the release profile compiles (release.yml ships the
|
||||
# `dist` profile), so skip LTO and spread codegen across more units.
|
||||
CARGO_PROFILE_RELEASE_LTO: "off"
|
||||
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "256"
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":hammer: cargo build --release" cargo build --workspace --locked --release'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":test_tube: Unit tests (Linux) %n"
|
||||
key: unit-test
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/unit-test"
|
||||
agents:
|
||||
queue: linux-large
|
||||
# Split with nextest --partition; raise this as the suite grows.
|
||||
parallelism: 2
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: &test_collector
|
||||
- test-collector#v1.12.0:
|
||||
files: test-results/junit.xml
|
||||
format: junit
|
||||
api-token-env-name: ATUIN_TEST_ENGINE_TOKEN
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":test_tube: cargo nextest (lib + bins)" cargo nextest run $$(nextest_partition) --lib --bins || { .buildkite/scripts/proptest-regressions.sh; exit 1; }'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":test_tube: Unit tests (macOS) %n"
|
||||
key: unit-test-macos
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/unit-test-macos"
|
||||
agents:
|
||||
queue: macos-large
|
||||
# Split with nextest --partition; raise this as the suite grows.
|
||||
parallelism: 2
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: *test_collector
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
# No rustup cache on macOS: the image ships several toolchains, so the
|
||||
# entry is 1.7 GB (20s to restore) against ~4s to install 1.98.0.
|
||||
- 'section ":test_tube: cargo nextest (lib + bins)" cargo nextest run $$(nextest_partition) --lib --bins || { .buildkite/scripts/proptest-regressions.sh; exit 1; }'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":mag: Check (Linux): all features, no features, release"
|
||||
key: check
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/check"
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":package: cargo fetch" cargo fetch --locked'
|
||||
# Registry contents are the same for every step; this one saves them.
|
||||
- buildkite-agent cache save --name cargo_registry
|
||||
# --all-features enables vendored-tls, which compiles OpenSSL from
|
||||
# source; type-checking can use the system libssl instead.
|
||||
- 'section ":mag: check (all features)" env OPENSSL_NO_VENDOR=1 cargo check --all-features --workspace'
|
||||
- 'section ":mag: check (no features)" cargo check --no-default-features --workspace'
|
||||
- 'section ":mag: check (release profile)" cargo check --workspace --locked --release'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
# The rest of rust.yml's check matrix, in parallel with the step above.
|
||||
- label: ":mag: Check (Linux): sync, server, client only"
|
||||
key: check-subsets
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/check-subsets"
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":mag: check (sync)" cargo check --no-default-features --features sync --workspace'
|
||||
- 'section ":mag: check (server)" cargo check -p atuin-server'
|
||||
- 'section ":mag: check (client only)" cargo check --no-default-features --features client --workspace'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":mag: Check (macOS)"
|
||||
key: check-macos
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/check-macos"
|
||||
agents:
|
||||
queue: macos-large
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
# No rustup cache on macOS: the image ships several toolchains, so the
|
||||
# entry is 1.7 GB (20s to restore) against ~4s to install 1.98.0.
|
||||
- 'section ":package: cargo fetch" cargo fetch --locked'
|
||||
# The registry key includes the OS, so macOS saves its own.
|
||||
- buildkite-agent cache save --name cargo_registry
|
||||
# No system libssl on macOS: --all-features builds vendored OpenSSL,
|
||||
# as it does on GitHub Actions.
|
||||
- 'section ":mag: check (all features)" cargo check --all-features --workspace'
|
||||
- 'section ":mag: check (no features)" cargo check --no-default-features --workspace'
|
||||
- 'section ":mag: check (sync)" cargo check --no-default-features --features sync --workspace'
|
||||
- 'section ":mag: check (server)" cargo check -p atuin-server'
|
||||
- 'section ":mag: check (client only)" cargo check --no-default-features --features client --workspace'
|
||||
- 'section ":mag: check (release profile)" cargo check --workspace --locked --release'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":crab: MSRV (1.95.0)"
|
||||
key: msrv
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/msrv"
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
# rust-toolchain.toml pins the dev toolchain; override it explicitly.
|
||||
RUST_TOOLCHAIN: "1.95.0"
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":crab: check on MSRV (all features)" cargo check --locked --workspace --all-features'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":postgres: Integration tests %n"
|
||||
key: integration-test
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/integration-test"
|
||||
agents:
|
||||
queue: linux-large
|
||||
# Split with nextest --partition; raise this as the suite grows.
|
||||
parallelism: 2
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
ATUIN_DB_URI: postgres://atuin:pass@localhost:5432/atuin
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: *test_collector
|
||||
command:
|
||||
- .buildkite/scripts/start-postgres.sh
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
# e2e tests have their own step. Test databases live in tempdirs;
|
||||
# tmpfs keeps concurrent SQLite WAL fsyncs from stalling the disk.
|
||||
- 'section ":test_tube: cargo nextest (integration)" env TMPDIR=/dev/shm cargo nextest run $$(nextest_partition) --test "*" -E "not binary(/e2e_.*/)" || { .buildkite/scripts/proptest-regressions.sh; exit 1; }'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":terminal: e2e (Linux) %n"
|
||||
key: e2e
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/e2e"
|
||||
# Split with nextest --partition; raise this as the suite grows.
|
||||
parallelism: 2
|
||||
agents:
|
||||
queue: linux-large
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
APT_PACKAGES: "zsh fish xz-utils"
|
||||
ATUIN_E2E_REQUIRE_SHELLS: "1"
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: *test_collector
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":shell: Install ble.sh" .buildkite/scripts/install-blesh.sh'
|
||||
- 'section ":terminal: cargo nextest (e2e)" cargo nextest run $$(nextest_partition) -p atuin --test "e2e_*"'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":terminal: e2e (macOS)"
|
||||
key: e2e-macos
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/e2e-macos"
|
||||
agents:
|
||||
queue: macos-large
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
BREW_PACKAGES: "bash fish"
|
||||
ATUIN_E2E_REQUIRE_SHELLS: "1"
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: *test_collector
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
# No rustup cache on macOS: the image ships several toolchains, so the
|
||||
# entry is 1.7 GB (20s to restore) against ~4s to install 1.98.0.
|
||||
# Test with Homebrew's bash, not macOS's /bin/bash 3.2.
|
||||
# ($$ escapes pipeline-upload interpolation.)
|
||||
- export ATUIN_E2E_BASH="$$(brew --prefix bash)/bin/bash"
|
||||
- 'section ":shell: Install ble.sh" .buildkite/scripts/install-blesh.sh'
|
||||
- 'section ":terminal: cargo nextest (e2e)" cargo nextest run -p atuin --test "e2e_*"'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":paperclip: Clippy"
|
||||
key: clippy
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/clippy"
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
RUST_COMPONENTS: clippy
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
# One run over the union of what `cargo clippy` (lib + bins) and
|
||||
# `cargo clippy --tests` lint, so the two share their work.
|
||||
- 'section ":paperclip: clippy (lib, bins, tests)" cargo clippy --lib --bins --tests -- -D warnings'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":books: Docs"
|
||||
key: docs
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/docs"
|
||||
timeout_in_minutes: 45
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":books: cargo doc" cargo doc --document-private-items --no-deps --workspace'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
- label: ":art: Format"
|
||||
key: format
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/format"
|
||||
timeout_in_minutes: 15
|
||||
env:
|
||||
RUST_TOOLCHAIN: nightly
|
||||
RUST_COMPONENTS: rustfmt
|
||||
command:
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- 'section ":art: cargo fmt --check" cargo fmt -- --check'
|
||||
|
||||
- label: ":no_entry: cargo-deny"
|
||||
key: deny
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/deny"
|
||||
timeout_in_minutes: 15
|
||||
env:
|
||||
CARGO_TOOLS: deny
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
# Same arguments cargo-deny-action passes by default.
|
||||
- 'section ":no_entry: cargo deny check" cargo deny --log-level warn --all-features check'
|
||||
# Library crates must never (re)add eyre or anyhow; each has its own
|
||||
# crate-scoped deny.toml.
|
||||
- 'section ":no_entry: bans: atuin-common" cargo deny --log-level warn --manifest-path crates/atuin-common/Cargo.toml --all-features check bans'
|
||||
- 'section ":no_entry: bans: atuin-history" cargo deny --log-level warn --manifest-path crates/atuin-history/Cargo.toml --all-features check bans'
|
||||
- 'section ":no_entry: bans: atuin-domain" cargo deny --log-level warn --manifest-path crates/atuin-domain/Cargo.toml --all-features check bans'
|
||||
- 'section ":no_entry: bans: atuin-pty-proxy" cargo deny --log-level warn --manifest-path crates/atuin-pty-proxy/Cargo.toml --all-features check bans'
|
||||
|
||||
- label: ":package: Package"
|
||||
key: package
|
||||
# main only: this only guards crates.io publishing (every crate builds
|
||||
# from its packaged tarball), which isn't part of the PR flow. On main
|
||||
# it still flags publishing breakage well before a release.
|
||||
if: build.branch == pipeline.default_branch
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/package"
|
||||
agents:
|
||||
queue: linux-large
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
# Make sure the packaged crates build from their tarballs, not from
|
||||
# anything left over from the workspace build. The restored target/
|
||||
# holds only third-party builds (prune-target.sh), which verification
|
||||
# reuses: it builds into the shared target/debug.
|
||||
- rm -rf ~/.cargo/registry/{cache,src}/*/atuin* target/package
|
||||
- 'section ":package: cargo package" cargo package --workspace'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
# rust.yml's "Test in VM (illumos)": builds the client and server on a real
|
||||
# OmniOS VM (KVM-accelerated; hosted agents expose /dev/kvm). main only,
|
||||
# like build and nix-build: it was ~12 min per PR on GitHub Actions.
|
||||
- label: ":sunrise: Build on illumos (OmniOS VM)"
|
||||
key: illumos
|
||||
if: build.branch == pipeline.default_branch
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/illumos"
|
||||
agents:
|
||||
queue: linux-large
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- buildkite-agent cache restore --name anyvm --name apt_debs
|
||||
- .buildkite/scripts/illumos.sh
|
||||
- buildkite-agent cache save --name anyvm --name apt_debs
|
||||
|
||||
- label: ":moon: Nightly tests"
|
||||
key: nightly
|
||||
if: build.source == "schedule"
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/nightly"
|
||||
timeout_in_minutes: 60
|
||||
env:
|
||||
CARGO_TOOLS: nextest
|
||||
secrets:
|
||||
- ATUIN_TEST_ENGINE_TOKEN
|
||||
plugins: *test_collector
|
||||
command:
|
||||
- buildkite-agent cache restore --name cargo_registry --name target --name rustup --name apt_debs
|
||||
- source .buildkite/scripts/toolchain.sh
|
||||
- buildkite-agent cache save --name rustup --name apt_debs
|
||||
- 'section ":moon: cargo nextest (nightly)" cargo nextest run -p atuin-daemon --test nightly --run-ignored all || { .buildkite/scripts/proptest-regressions.sh; exit 1; }'
|
||||
- 'section ":broom: Prune target/" .buildkite/scripts/prune-target.sh'
|
||||
- buildkite-agent cache save --name target
|
||||
|
||||
# nix.yml: flake check, build (and run the result), formatting.
|
||||
- label: ":nix: Nix flake check"
|
||||
key: nix-check
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/nix-check"
|
||||
timeout_in_minutes: 30
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":nix: Install Nix" install_nix'
|
||||
- 'section ":nix: nix flake check" nix flake check --print-build-logs'
|
||||
|
||||
- label: ":nix: Nix build"
|
||||
key: nix-build
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/nix-build"
|
||||
# main only: buildRustPackage builds atuin and every dependency in one
|
||||
# derivation, so every change rebuilds from scratch (~4 min). The flake
|
||||
# check and nixfmt steps still run on PRs.
|
||||
if: build.branch == pipeline.default_branch
|
||||
agents:
|
||||
queue: linux-large
|
||||
timeout_in_minutes: 45
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":nix: Install Nix" install_nix'
|
||||
- 'section ":nix: nix build" nix build --print-build-logs'
|
||||
- 'section ":nix: Run the built binary" ./result/bin/atuin --version'
|
||||
|
||||
- label: ":nix: nixfmt"
|
||||
key: nixfmt
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/nixfmt"
|
||||
timeout_in_minutes: 15
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":nix: Install Nix" install_nix'
|
||||
- 'section ":nix: nix fmt --ci" nix fmt -- --ci'
|
||||
|
||||
- label: ":pencil2: Codespell"
|
||||
key: codespell
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/codespell"
|
||||
timeout_in_minutes: 10
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":pencil2: Install codespell" install_codespell'
|
||||
- 'section ":pencil2: codespell" codespell --count --exclude-file CHANGELOG.md'
|
||||
|
||||
- label: ":shell: Shellcheck"
|
||||
key: shellcheck
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/shellcheck"
|
||||
timeout_in_minutes: 10
|
||||
env:
|
||||
SHELLCHECK_OPTS: "-e SC2148"
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":shell: Install ShellCheck" install_shellcheck'
|
||||
- 'section ":shell: shellcheck" .buildkite/scripts/shellcheck.sh'
|
||||
|
||||
# vale.yml ran only on pull requests touching the docs or the Vale config.
|
||||
# On main builds there's no diff against main, so if_changed skips it there
|
||||
# too.
|
||||
- label: ":memo: Vale (docs prose)"
|
||||
key: vale
|
||||
if_changed:
|
||||
- "docs/docs/**"
|
||||
- ".vale.ini"
|
||||
- ".vale/styles/config/**"
|
||||
- ".buildkite/scripts/vale.sh"
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/vale"
|
||||
timeout_in_minutes: 10
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":memo: Install Vale" install_vale'
|
||||
- 'section ":memo: vale" .buildkite/scripts/vale.sh'
|
||||
|
||||
# installer.yml: the published installer (setup.atuin.sh, served from
|
||||
# main's install.sh), so it runs on main builds only, as before.
|
||||
- label: ":inbox_tray: Installer (Linux)"
|
||||
key: install
|
||||
if: build.branch == pipeline.default_branch
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/install"
|
||||
timeout_in_minutes: 10
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":shell: Install zsh" apt_install zsh'
|
||||
- 'section ":inbox_tray: Installer via bash" .buildkite/scripts/test-installer.sh bash'
|
||||
- 'section ":inbox_tray: Installer via zsh" .buildkite/scripts/test-installer.sh zsh'
|
||||
|
||||
- label: ":inbox_tray: Installer (macOS)"
|
||||
key: install-macos
|
||||
if: build.branch == pipeline.default_branch
|
||||
notify:
|
||||
- github_check:
|
||||
name: "buildkite/install-macos"
|
||||
agents:
|
||||
queue: macos-medium
|
||||
timeout_in_minutes: 10
|
||||
command:
|
||||
- source .buildkite/scripts/lib.sh
|
||||
- 'section ":inbox_tray: Installer via bash" .buildkite/scripts/test-installer.sh bash'
|
||||
- 'section ":inbox_tray: Installer via zsh" .buildkite/scripts/test-installer.sh zsh'
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds atuin-client and atuin-server on a real illumos (OmniOS) VM, as
|
||||
# rust.yml's "Test in VM (illumos)" job did with vmactions/omnios-vm, which
|
||||
# runs anyvm.py under the hood. Hosted Linux agents expose /dev/kvm, so the
|
||||
# VM is KVM-accelerated. anyvm exits with the guest command's status.
|
||||
set -euo pipefail
|
||||
|
||||
# The versions vmactions/omnios-vm pins for this release
|
||||
# (conf/r151054-build.conf).
|
||||
ANYVM_VERSION=0.6.5
|
||||
ANYVM_SHA256=24fbcf739fc07d7fd655f493481cb77ed022b4baa81b039227080e1a7aaaa921
|
||||
OMNIOS_RELEASE=r151054-build
|
||||
BUILDER_VERSION=2.1.3
|
||||
RUST_VERSION=1.98.0
|
||||
|
||||
# shellcheck source=lib.sh disable=SC1091
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
section ":package: Install QEMU" \
|
||||
apt_install qemu-system-x86 qemu-utils zstd xz-utils rsync openssh-client
|
||||
section ":package: Fetch anyvm ${ANYVM_VERSION}" \
|
||||
fetch_verified "https://github.com/anyvm-org/anyvm/releases/download/v${ANYVM_VERSION}/anyvm.py" \
|
||||
"$ANYVM_SHA256" /tmp/anyvm.py
|
||||
|
||||
# Run inside the VM: the same steps as the old job's prepare + run.
|
||||
guest_script="
|
||||
set -e
|
||||
cd /work
|
||||
echo '--- :sunrise: Prepare OmniOS'
|
||||
# pkg exits 4 when there is nothing to install.
|
||||
pkg install pkg-config openssl || [ \$? -eq 4 ]
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
|
||||
sh -s -- -y --profile minimal --default-toolchain ${RUST_VERSION}
|
||||
. \"\$HOME/.cargo/env\"
|
||||
echo '--- :hammer: cargo build -p atuin-client --release'
|
||||
cargo build -p atuin-client --locked --release
|
||||
echo '--- :hammer: cargo build -p atuin-server --release'
|
||||
cargo build -p atuin-server --locked --release
|
||||
"
|
||||
|
||||
# The downloaded image lives in the `anyvm` cache; --snapshot runs from it
|
||||
# without copying and discards the VM's disk writes.
|
||||
echo "--- :sunrise: Boot OmniOS ${OMNIOS_RELEASE} and build"
|
||||
python3 /tmp/anyvm.py \
|
||||
--os omnios --release "$OMNIOS_RELEASE" --builder "$BUILDER_VERSION" \
|
||||
--cpu "$(nproc)" --mem 16384 \
|
||||
--cache-dir "$HOME/.cache/anyvm" --data-dir /tmp/anyvm-data --snapshot \
|
||||
--vnc off \
|
||||
--sync rsync -v "$PWD:/work" \
|
||||
-- sh -c "$guest_script"
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs ble.sh where the e2e tests expect it (~/.local/share/blesh).
|
||||
set -euo pipefail
|
||||
|
||||
version=0.4.0-devel3
|
||||
mkdir -p "$HOME/.local/share"
|
||||
curl -fsSL "https://github.com/akinomyoga/ble.sh/releases/download/v${version}/ble-${version}.tar.xz" |
|
||||
tar xJ -C "$HOME/.local/share"
|
||||
rm -rf "$HOME/.local/share/blesh"
|
||||
mv "$HOME/.local/share/ble-${version}" "$HOME/.local/share/blesh"
|
||||
@@ -0,0 +1,120 @@
|
||||
# Shared helpers, sourced (not executed) by toolchain.sh and by the steps
|
||||
# that don't need Rust. Runs on Linux and macOS hosted agents, so it sticks
|
||||
# to bash 3.2 (macOS's /bin/bash).
|
||||
|
||||
# Runs a command under its own collapsible log group. Buildkite groups all
|
||||
# output after a `--- title` line until the next one; on failure `^^^ +++`
|
||||
# expands the group so the error is visible.
|
||||
section() {
|
||||
local title=$1
|
||||
shift
|
||||
echo "--- $title"
|
||||
"$@" && return 0
|
||||
local status=$?
|
||||
echo "^^^ +++"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
verify_sha256() { # <sha256> <file>
|
||||
if command -v sha256sum >/dev/null; then
|
||||
echo "$1 $2" | sha256sum -c -
|
||||
else
|
||||
echo "$1 $2" | shasum -a 256 -c -
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_verified() { # <url> <sha256> <file>
|
||||
curl -fsSL -o "$3" "$1"
|
||||
verify_sha256 "$2" "$3"
|
||||
}
|
||||
|
||||
# Downloaded .debs, kept by the `apt_debs` cache so later jobs can install
|
||||
# them with dpkg instead of paying ~5s for `apt-get update` + download.
|
||||
APT_DEBS_DIR="$HOME/.cache/apt-debs"
|
||||
mkdir -p "$APT_DEBS_DIR/partial"
|
||||
|
||||
# Installs whichever of the given apt packages are missing (Linux only).
|
||||
apt_install() {
|
||||
local sudo="" missing="" pkg
|
||||
[ "$(id -u)" -eq 0 ] || sudo=sudo
|
||||
for pkg in "$@"; do
|
||||
dpkg -s "$pkg" >/dev/null 2>&1 || missing="$missing $pkg"
|
||||
done
|
||||
[ -n "$missing" ] || return 0
|
||||
# Fast path: the .debs a previous job downloaded. --refuse-downgrade makes
|
||||
# a newer base image fall through to apt rather than downgrade its
|
||||
# packages to the cached versions.
|
||||
if ls "$APT_DEBS_DIR"/*.deb >/dev/null 2>&1 &&
|
||||
$sudo dpkg -i --refuse-downgrade "$APT_DEBS_DIR"/*.deb >/dev/null; then
|
||||
echo "Installed$missing from cached .debs"
|
||||
return 0
|
||||
fi
|
||||
# Download into APT_DEBS_DIR (not /var/cache/apt/archives, which images
|
||||
# often clean after every install) so they can be cached. -f repairs
|
||||
# anything a failed dpkg fast path left half-configured.
|
||||
$sudo apt-get update -qq
|
||||
# shellcheck disable=SC2086 # word-split the package list
|
||||
$sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y -qq -f --no-install-recommends \
|
||||
-o Dir::Cache::archives="$APT_DEBS_DIR" -o APT::Keep-Downloaded-Packages=true $missing
|
||||
}
|
||||
|
||||
# nextest's `--partition count:K/N` for a step split with Buildkite's
|
||||
# `parallelism: N`, and nothing for a single job, so how many ways a test
|
||||
# step is split is just that one number in the pipeline.
|
||||
nextest_partition() {
|
||||
if [ -n "${BUILDKITE_PARALLEL_JOB_COUNT:-}" ]; then
|
||||
echo "--partition count:$((BUILDKITE_PARALLEL_JOB + 1))/${BUILDKITE_PARALLEL_JOB_COUNT}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prebuilt tools not tied to Rust go here.
|
||||
TOOLS_BIN="$HOME/.local/bin"
|
||||
mkdir -p "$TOOLS_BIN"
|
||||
export PATH="$TOOLS_BIN:$PATH"
|
||||
|
||||
SHELLCHECK_VERSION=0.11.0
|
||||
SHELLCHECK_SHA256=b7af85e41cc99489dcc21d66c6d5f3685138f06d34651e6d34b42ec6d54fe6f6
|
||||
VALE_VERSION=3.15.2
|
||||
VALE_SHA256=fc72e64454d6bd7af91905d4faebbf411bae3eec17bb572f4101311212bc0d9e
|
||||
CODESPELL_VERSION=2.4.3
|
||||
NIX_INSTALLER_VERSION=3.22.5
|
||||
NIX_INSTALLER_SHA256=9432d40ec1d0d4ebb6284848cc389bdb1bbf7aeb5c1cea11e7efc9daf0a1ec82
|
||||
|
||||
install_shellcheck() {
|
||||
fetch_verified "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.gz" \
|
||||
"$SHELLCHECK_SHA256" /tmp/shellcheck.tgz
|
||||
tar -xzf /tmp/shellcheck.tgz -C /tmp
|
||||
mv "/tmp/shellcheck-v${SHELLCHECK_VERSION}/shellcheck" "$TOOLS_BIN/"
|
||||
shellcheck --version | sed -n 2p
|
||||
}
|
||||
|
||||
install_vale() {
|
||||
fetch_verified "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/vale_${VALE_VERSION}_Linux_64-bit.tar.gz" \
|
||||
"$VALE_SHA256" /tmp/vale.tgz
|
||||
tar -xzf /tmp/vale.tgz -C "$TOOLS_BIN" vale
|
||||
vale --version
|
||||
}
|
||||
|
||||
# In its own venv: Ubuntu's system Python refuses `pip install` (PEP 668).
|
||||
install_codespell() {
|
||||
if ! python3 -m venv /tmp/codespell 2>/dev/null; then
|
||||
apt_install python3-venv
|
||||
python3 -m venv /tmp/codespell
|
||||
fi
|
||||
/tmp/codespell/bin/pip install --quiet "codespell==${CODESPELL_VERSION}"
|
||||
ln -sf /tmp/codespell/bin/codespell "$TOOLS_BIN/codespell"
|
||||
codespell --version
|
||||
}
|
||||
|
||||
# Upstream Nix (what cachix/install-nix-action installs), root-only with no
|
||||
# daemon (`--init none`), so it doesn't depend on the image running systemd.
|
||||
install_nix() {
|
||||
local sudo=""
|
||||
[ "$(id -u)" -eq 0 ] || sudo=sudo
|
||||
fetch_verified "https://github.com/DeterminateSystems/nix-installer/releases/download/v${NIX_INSTALLER_VERSION}/nix-installer-x86_64-linux" \
|
||||
"$NIX_INSTALLER_SHA256" /tmp/nix-installer
|
||||
chmod +x /tmp/nix-installer
|
||||
$sudo /tmp/nix-installer install linux --no-confirm --init none --prefer-upstream-nix
|
||||
export PATH="/nix/var/nix/profiles/default/bin:$PATH"
|
||||
nix --version
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run after a failed test step: a proptest failure appends a `cc <seed>` line
|
||||
# to a regression file, so show which ones changed and upload them as build
|
||||
# artifacts to reproduce locally.
|
||||
set -uo pipefail
|
||||
|
||||
echo "+++ :warning: proptest regression files changed by this run"
|
||||
git add -N -- '*proptest-regressions*' 2>/dev/null || true
|
||||
if git status --porcelain -- '*proptest-regressions*' | grep -q .; then
|
||||
git status --porcelain -- '*proptest-regressions*'
|
||||
git --no-pager diff -- '*proptest-regressions*'
|
||||
buildkite-agent artifact upload '**/proptest-regressions/**;**/*.proptest-regressions'
|
||||
else
|
||||
echo "None."
|
||||
fi
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Trims target/ down to third-party dependency artifacts before it's saved to
|
||||
# the Buildkite cache (the same idea as Swatinem/rust-cache's cleanup):
|
||||
#
|
||||
# * workspace crates always rebuild anyway (a fresh checkout gives every
|
||||
# source file a new mtime), so their artifacts are dead weight;
|
||||
# * incremental/ is unused (CARGO_INCREMENTAL=0) and doc/, package/ and
|
||||
# nextest/ are per-run output;
|
||||
# * final binaries at the top of each profile dir are rebuilt every run.
|
||||
#
|
||||
# Keeps each cache entry to roughly the size of the dependency build.
|
||||
set -euo pipefail
|
||||
|
||||
target=${CARGO_TARGET_DIR:-target}
|
||||
[ -d "$target" ] || exit 0
|
||||
|
||||
before=$(du -sh "$target" | cut -f1)
|
||||
|
||||
# Matching is done in Python (already needed to read cargo metadata) so it's
|
||||
# exact and identical on Linux and macOS: an artifact belongs to the
|
||||
# workspace only if its name is `<workspace name>-<16 hex hash>`, so e.g. the
|
||||
# crates.io crate atuin-vt100 survives even though `atuin` is a workspace
|
||||
# crate.
|
||||
cargo metadata --no-deps --format-version 1 --offline | python3 -c '
|
||||
import json, os, re, shutil, sys
|
||||
|
||||
target = sys.argv[1]
|
||||
meta = json.load(sys.stdin)
|
||||
names = set()
|
||||
for pkg in meta["packages"]:
|
||||
names |= {pkg["name"], pkg["name"].replace("-", "_")}
|
||||
names |= {t["name"].replace("-", "_") for t in pkg["targets"]}
|
||||
|
||||
artifact = re.compile(r"^(?:lib)?(?P<name>.+?)-[0-9a-f]{16}(?:\..*)?$")
|
||||
|
||||
def remove(path):
|
||||
if os.path.isdir(path) and not os.path.islink(path):
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
os.remove(path)
|
||||
|
||||
for per_run in ("doc", "package", "nextest", "tmp"):
|
||||
path = os.path.join(target, per_run)
|
||||
if os.path.exists(path):
|
||||
remove(path)
|
||||
|
||||
for profile in os.listdir(target):
|
||||
profile_dir = os.path.join(target, profile)
|
||||
if not os.path.isdir(os.path.join(profile_dir, "deps")):
|
||||
continue
|
||||
for per_run in ("incremental", "examples"):
|
||||
path = os.path.join(profile_dir, per_run)
|
||||
if os.path.exists(path):
|
||||
remove(path)
|
||||
# Top-level outputs (binaries, .d files) are copies of deps/ artifacts.
|
||||
for entry in os.listdir(profile_dir):
|
||||
path = os.path.join(profile_dir, entry)
|
||||
if os.path.isfile(path) or os.path.islink(path):
|
||||
os.remove(path)
|
||||
for sub in ("deps", ".fingerprint", "build"):
|
||||
sub_dir = os.path.join(profile_dir, sub)
|
||||
if not os.path.isdir(sub_dir):
|
||||
continue
|
||||
for entry in os.listdir(sub_dir):
|
||||
match = artifact.match(entry)
|
||||
if match and match.group("name") in names:
|
||||
remove(os.path.join(sub_dir, entry))
|
||||
' "$target"
|
||||
|
||||
echo "Pruned $target: $before -> $(du -sh "$target" | cut -f1)"
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# The file discovery and invocation of ludeeus/[email protected], as
|
||||
# .github/workflows/shellcheck.yml ran it: every shell file by name, plus
|
||||
# extensionless executables with an sh/bash/ksh shebang, each checked
|
||||
# separately with SHELLCHECK_OPTS from the environment.
|
||||
set -uo pipefail
|
||||
|
||||
excludes=(! -path '*./.git/*' ! -path '*.go' ! -path '*/mvnw')
|
||||
files=()
|
||||
while IFS= read -r -d '' file; do
|
||||
files+=("$file")
|
||||
done < <(find . "${excludes[@]}" -type f '(' \
|
||||
-name '*.bash' -o -name '.bashrc' -o -name 'bashrc' -o -name '.bash_aliases' \
|
||||
-o -name '.bash_completion' -o -name '.bash_login' -o -name '.bash_logout' \
|
||||
-o -name '.bash_profile' -o -name 'bash_profile' -o -name '*.ksh' \
|
||||
-o -name 'suid_profile' -o -name '*.zsh' -o -name '.zlogin' -o -name 'zlogin' \
|
||||
-o -name '.zlogout' -o -name 'zlogout' -o -name '.zprofile' -o -name 'zprofile' \
|
||||
-o -name '.zsenv' -o -name 'zsenv' -o -name '.zshrc' -o -name 'zshrc' \
|
||||
-o -name '*.sh' -o -path '*/.profile' -o -path '*/profile' -o -name '*.shlib' \
|
||||
')' -print0)
|
||||
while IFS= read -r -d '' file; do
|
||||
head -n1 "$file" | grep -Eqs '^#! */[^ ]*/(env *)?[abk]*sh' || continue
|
||||
files+=("$file")
|
||||
done < <(find . "${excludes[@]}" -type f ! -name '*.*' -perm /111 -print0)
|
||||
|
||||
echo "Checking ${#files[@]} files"
|
||||
status=0
|
||||
for file in "${files[@]}"; do
|
||||
shellcheck --format=gcc "$file" || status=$?
|
||||
done
|
||||
exit "$status"
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Starts Postgres for the integration tests on localhost:5432 (the
|
||||
# ATUIN_DB_URI the step sets) and waits until it accepts connections.
|
||||
set -euo pipefail
|
||||
|
||||
echo "--- :postgres: Starting Postgres"
|
||||
docker run -d --name postgres \
|
||||
-e POSTGRES_USER=atuin -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=atuin \
|
||||
-p 5432:5432 \
|
||||
postgres:18-alpine >/dev/null
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
if docker exec postgres pg_isready -U atuin >/dev/null 2>&1; then
|
||||
echo "Postgres is ready"
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
docker logs postgres
|
||||
echo "+++ :rotating_light: Postgres did not become ready"
|
||||
exit 1
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs the published installer (setup.atuin.sh, served from this repo's
|
||||
# install.sh on main) under the given shell and checks atuin runs, as
|
||||
# .github/workflows/installer.yml did.
|
||||
set -euo pipefail
|
||||
|
||||
shell=$1
|
||||
# Single-quoted on purpose: the inner shell expands it, not this one.
|
||||
# shellcheck disable=SC2016
|
||||
"$shell" -c '
|
||||
/bin/bash -c "$(curl --proto "=https" --tlsv1.2 -sSf https://setup.atuin.sh)"
|
||||
[ -d "$HOME/.atuin" ] && . "$HOME/.atuin/bin/env"
|
||||
atuin --help
|
||||
'
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
# Sourced (not executed) at the start of each Rust step: installs the Rust
|
||||
# toolchain and CI tools. Shared helpers (`section` etc.) come from lib.sh.
|
||||
#
|
||||
# Per-step knobs, set as step env:
|
||||
# RUST_TOOLCHAIN toolchain to install (default: rust-toolchain.toml)
|
||||
# RUST_COMPONENTS extra rustup components, e.g. "clippy"
|
||||
# CARGO_TOOLS prebuilt tools to install: "nextest", "deny"
|
||||
# APT_PACKAGES extra apt packages on Linux (libssl-dev + pkg-config always)
|
||||
# BREW_PACKAGES Homebrew packages on macOS
|
||||
#
|
||||
# Runs on Linux and macOS hosted agents, so it sticks to bash 3.2 (macOS's
|
||||
# /bin/bash).
|
||||
|
||||
# lib.sh is checked on its own; shellcheck only follows sources with -x.
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
|
||||
NEXTEST_VERSION=0.9.146
|
||||
NEXTEST_LINUX_SHA256=682c21b777c333e96fd532e114d3a5a894e0729ab88d94c0a9f20f8419695428
|
||||
NEXTEST_MACOS_SHA256=39785160b3c2f6ed9a765049cf4fa79f3b39aa02eb7598a5a0e2a1a0b9ffb9a8
|
||||
CARGO_DENY_VERSION=0.20.2
|
||||
CARGO_DENY_SHA256=9f12ed4c49936e09b48bf862b595cde2fe64fcbd9d74dfacac6131ca824c8d5f
|
||||
|
||||
echo "--- :rust: Toolchain"
|
||||
export CARGO_HOME="$HOME/.cargo" RUSTUP_HOME="$HOME/.rustup"
|
||||
export PATH="$CARGO_HOME/bin:$PATH"
|
||||
export CARGO_TERM_COLOR=always CARGO_INCREMENTAL=0
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
# shellcheck disable=SC2086 # word-split the package list
|
||||
apt_install libssl-dev pkg-config ${APT_PACKAGES:-}
|
||||
nextest_asset=x86_64-unknown-linux-gnu nextest_sha256=$NEXTEST_LINUX_SHA256
|
||||
;;
|
||||
Darwin)
|
||||
if [ -n "${BREW_PACKAGES:-}" ]; then
|
||||
# shellcheck disable=SC2086 # word-split the package list
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install $BREW_PACKAGES
|
||||
fi
|
||||
nextest_asset=universal-apple-darwin nextest_sha256=$NEXTEST_MACOS_SHA256
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS: $(uname -s)"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! command -v rustup >/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
|
||||
sh -s -- -y --no-modify-path --profile minimal --default-toolchain none
|
||||
fi
|
||||
if [ -n "${RUST_TOOLCHAIN:-}" ]; then
|
||||
rustup toolchain install "$RUST_TOOLCHAIN" --profile minimal
|
||||
export RUSTUP_TOOLCHAIN="$RUST_TOOLCHAIN"
|
||||
else
|
||||
# The channel pinned in rust-toolchain.toml. Named explicitly: a bare
|
||||
# `rustup toolchain install` needs rustup 1.28+, and hosted images may
|
||||
# ship an older one.
|
||||
channel=$(sed -n 's/^channel *= *"\(.*\)"/\1/p' rust-toolchain.toml)
|
||||
rustup toolchain install "$channel" --profile minimal
|
||||
fi
|
||||
for component in ${RUST_COMPONENTS:-}; do
|
||||
rustup component add "$component"
|
||||
done
|
||||
|
||||
# rustup may come preinstalled from elsewhere (a system package, the image),
|
||||
# in which case nothing has created $CARGO_HOME/bin yet.
|
||||
mkdir -p "$CARGO_HOME/bin"
|
||||
for tool in ${CARGO_TOOLS:-}; do
|
||||
case "$tool" in
|
||||
nextest)
|
||||
fetch_verified "https://github.com/nextest-rs/nextest/releases/download/cargo-nextest-${NEXTEST_VERSION}/cargo-nextest-${NEXTEST_VERSION}-${nextest_asset}.tar.gz" \
|
||||
"$nextest_sha256" /tmp/nextest.tgz
|
||||
tar -xzf /tmp/nextest.tgz -C "$CARGO_HOME/bin"
|
||||
;;
|
||||
deny)
|
||||
fetch_verified "https://github.com/EmbarkStudios/cargo-deny/releases/download/${CARGO_DENY_VERSION}/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
|
||||
"$CARGO_DENY_SHA256" /tmp/cargo-deny.tgz
|
||||
tar -xzf /tmp/cargo-deny.tgz -C /tmp
|
||||
mv "/tmp/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl/cargo-deny" "$CARGO_HOME/bin/"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown CARGO_TOOLS entry: $tool"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rustc --version
|
||||
cargo --version
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lints the docs prose as .github/workflows/vale.yml did: vale-action with
|
||||
# `--minAlertLevel=warning` and reviewdog's fail_on_error, which fails on any
|
||||
# reported alert. Vale alone only exits non-zero on errors, so fail on any
|
||||
# output instead.
|
||||
set -euo pipefail
|
||||
|
||||
vale sync
|
||||
out=$(vale --output=line --minAlertLevel=warning docs/docs || true)
|
||||
if [ -n "$out" ]; then
|
||||
echo "$out"
|
||||
echo "+++ :rotating_light: Vale reported $(printf '%s\n' "$out" | wc -l | tr -d ' ') warning(s) or error(s)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Vale: no warnings or errors."
|
||||
@@ -16,3 +16,12 @@ retries = 3
|
||||
# most one heavy case runs at a time with a few lightweight neighbours.
|
||||
filter = 'binary_id(=atuin-daemon::scale) and test(/hundred_k/)'
|
||||
threads-required = 4
|
||||
|
||||
# CI profile (Buildkite sets NEXTEST_PROFILE=ci). Inherits the default
|
||||
# profile's settings and overrides; adds a JUnit report for Buildkite Test
|
||||
# Engine.
|
||||
[profile.ci.junit]
|
||||
# Relative to target/nextest/ci/. Kept outside target/ so prune-target.sh
|
||||
# (which deletes target/nextest/) runs before the test-collector plugin's
|
||||
# pre-exit upload without taking the report with it.
|
||||
path = "../../../test-results/junit.xml"
|
||||
|
||||
@@ -26,3 +26,4 @@ test-behavior.sh
|
||||
# vocabulary under .vale/styles/config/ is source.
|
||||
/.vale/styles/*
|
||||
!/.vale/styles/config/
|
||||
test-results/
|
||||
|
||||
@@ -430,16 +430,6 @@ impl Sqlite {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all, fields(id = ?id), err)]
|
||||
async fn delete_row_raw(
|
||||
tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||
id: HistoryId,
|
||||
) -> Result<()> {
|
||||
db::query("delete from history where id = ?1").bind(id).execute(&mut **tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all, fields(id = ?h.id), err)]
|
||||
pub async fn save(&self, h: &History) -> Result<()> {
|
||||
debug!("saving history to sqlite");
|
||||
@@ -1020,10 +1010,21 @@ impl Sqlite {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// One `in (...)` statement per chunk that fits the bind-parameter limit, not one
|
||||
// statement per id: per-row deletes made removing 200k entries take ~13s.
|
||||
let ids_per_delete = self.sqlite.info().await.variable_number_limit().max(1);
|
||||
|
||||
let mut tx = self.sqlite.pool().begin().await?;
|
||||
|
||||
for id in ids {
|
||||
Self::delete_row_raw(&mut tx, id).await?;
|
||||
while ids.peek().is_some() {
|
||||
let mut builder = sqlx::QueryBuilder::new("delete from history where id in (");
|
||||
let mut list = builder.separated(", ");
|
||||
for id in ids.by_ref().take(ids_per_delete) {
|
||||
list.push_bind(id);
|
||||
}
|
||||
builder.push(")");
|
||||
|
||||
builder.build().execute(&mut *tx).await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -133,6 +133,42 @@ const BUILD_BATCH_SIZE: NonZeroUsize = NonZeroUsize::new(5000).unwrap();
|
||||
/// wait on the lock and how much work a lost race throws away.
|
||||
const APPEND_BATCH_SIZE: usize = 1000;
|
||||
|
||||
/// Below this many records, [`encrypt_records`] encrypts inline rather than fanning out.
|
||||
const PARALLEL_ENCRYPT_MIN: usize = 64;
|
||||
|
||||
/// Encrypt `records`, in order, spread across the blocking pool. Every record gets its own content
|
||||
/// key and key wrap, so encrypting a large batch one record at a time is CPU-bound on a single
|
||||
/// core: it was most of the time a 200k-entry delete spent writing tombstones.
|
||||
async fn encrypt_records(
|
||||
records: Vec<Record<DecryptedData>>,
|
||||
key: &paseto_v4::Key,
|
||||
) -> Result<Vec<Record<paseto_v4::EncryptedData>>> {
|
||||
if records.len() < PARALLEL_ENCRYPT_MIN {
|
||||
return Ok(records.iter().map(|record| record.encrypt(key)).collect());
|
||||
}
|
||||
|
||||
let tasks = std::thread::available_parallelism().map_or(1, NonZeroUsize::get);
|
||||
let per_task = records.len().div_ceil(tasks);
|
||||
let mut records = records.into_iter();
|
||||
let mut handles = Vec::with_capacity(tasks);
|
||||
loop {
|
||||
let part: Vec<_> = records.by_ref().take(per_task).collect();
|
||||
if part.is_empty() {
|
||||
break;
|
||||
}
|
||||
let key = key.clone();
|
||||
handles.push(tokio::task::spawn_blocking(move || {
|
||||
part.iter().map(|record| record.encrypt(&key)).collect::<Vec<_>>()
|
||||
}));
|
||||
}
|
||||
|
||||
let mut encrypted = Vec::with_capacity(per_task * handles.len());
|
||||
for handle in handles {
|
||||
encrypted.extend(handle.await?);
|
||||
}
|
||||
Ok(encrypted)
|
||||
}
|
||||
|
||||
/// How many records `incremental_build` decodes concurrently. Decoding is read-then-decrypt per
|
||||
/// record; overlapping the reads keeps the record store's pool busy without unbounded fan-out.
|
||||
/// Kept under the store's connection pool size so decodes don't starve other readers.
|
||||
@@ -192,7 +228,7 @@ impl HistoryStore {
|
||||
loop {
|
||||
let idx = self.store.last(&series).await?.map_or(0, |p| p.idx + 1);
|
||||
|
||||
let encrypted: Vec<_> = chunk
|
||||
let unencrypted: Vec<_> = chunk
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(n, bytes)| {
|
||||
@@ -203,9 +239,9 @@ impl HistoryStore {
|
||||
.idx(idx + u64::conv(n))
|
||||
.data(bytes.clone())
|
||||
.build()
|
||||
.encrypt(&self.encryption_key)
|
||||
})
|
||||
.collect();
|
||||
let encrypted = encrypt_records(unencrypted, &self.encryption_key).await?;
|
||||
|
||||
if self.store.push_batch_unique(encrypted.iter()).await? {
|
||||
ids.extend(encrypted.into_iter().map(|r| r.id));
|
||||
|
||||
@@ -57,29 +57,37 @@ impl super::Schema for Schema {
|
||||
}
|
||||
|
||||
async fn remove(db: &Sqlite, ids: impl Iterator<Item = HistoryId>) -> Result<(), IndexError> {
|
||||
let mut ids = ids.peekable();
|
||||
|
||||
if ids.peek().is_none() {
|
||||
let keys: Vec<[u8; 16]> = ids.map(HistoryId::into_bytes).collect();
|
||||
if keys.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// A chunk of ids per statement rather than two statements per id: a bulk history delete
|
||||
// passes every id, most of which never had output indexed, and 400k single-row
|
||||
// statements made removing 200k ids take ~20s.
|
||||
let keys_per_delete = db.info().await.variable_number_limit().max(1);
|
||||
|
||||
let pool = db.pool();
|
||||
let mut tx = pool.begin_with("BEGIN IMMEDIATE").await.map_err(store)?;
|
||||
for id in ids {
|
||||
let key = id.into_bytes();
|
||||
db::query(
|
||||
"DELETE FROM output_fts WHERE rowid = (SELECT id FROM indexed WHERE history_id = \
|
||||
?)",
|
||||
)
|
||||
.bind(&key[..])
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(store)?;
|
||||
db::query("DELETE FROM indexed WHERE history_id = ?")
|
||||
.bind(&key[..])
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(store)?;
|
||||
for chunk in keys.chunks(keys_per_delete) {
|
||||
let mut fts = sqlx::QueryBuilder::new(
|
||||
"DELETE FROM output_fts WHERE rowid IN (SELECT id FROM indexed WHERE history_id \
|
||||
IN (",
|
||||
);
|
||||
let mut list = fts.separated(", ");
|
||||
for key in chunk {
|
||||
list.push_bind(&key[..]);
|
||||
}
|
||||
fts.push("))");
|
||||
fts.build().execute(&mut *tx).await.map_err(store)?;
|
||||
|
||||
let mut indexed = sqlx::QueryBuilder::new("DELETE FROM indexed WHERE history_id IN (");
|
||||
let mut list = indexed.separated(", ");
|
||||
for key in chunk {
|
||||
list.push_bind(&key[..]);
|
||||
}
|
||||
indexed.push(")");
|
||||
indexed.build().execute(&mut *tx).await.map_err(store)?;
|
||||
}
|
||||
tx.commit().await.map_err(store)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user