Compare commits
41
Commits
main
...
ci/sandbox
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdecfc8d35 | ||
|
|
c53cadb3a4 | ||
|
|
dc4d823385 | ||
|
|
50ba161062 | ||
|
|
f25cd5324a | ||
|
|
55711d34a5 | ||
|
|
a1d9bcc7dd | ||
|
|
60996de947 | ||
|
|
1ebfa86dc6 | ||
|
|
a410495daa | ||
|
|
ba41deb9ec | ||
|
|
fb253f9b27 | ||
|
|
2ba284f326 | ||
|
|
064818c973 | ||
|
|
c182c8d4fc | ||
|
|
a2c8f59b5c | ||
|
|
9880ee36cd | ||
|
|
7d6c6783e9 | ||
|
|
acc061e752 | ||
|
|
c1293bda4c | ||
|
|
f4db1e7013 | ||
|
|
79ea99e939 | ||
|
|
45d3d3c91f | ||
|
|
92f6412539 | ||
|
|
07b072e5b7 | ||
|
|
1acdc44f88 | ||
|
|
393bac993d | ||
|
|
ff2270a64d | ||
|
|
f45ca8ff95 | ||
|
|
37d70c40c5 | ||
|
|
1c596cf097 | ||
|
|
16d63d95cb | ||
|
|
d4ca8fe7c5 | ||
|
|
6f8456f2b6 | ||
|
|
571c8ad70c | ||
|
|
5396dd1d7e | ||
|
|
a71b188289 | ||
|
|
31264c17c9 | ||
|
|
4a645bab2d | ||
|
|
439b5c53a8 | ||
|
|
25dd8e1f28 |
No files matched your search
+8
-6
@@ -12,21 +12,21 @@ target: rpilinux-image
|
|||||||
|
|
||||||
repos:
|
repos:
|
||||||
meta-raspberrypi:
|
meta-raspberrypi:
|
||||||
url: http://git.yoctoproject.org/git/meta-raspberrypi
|
url: https://git.yoctoproject.org/git/meta-raspberrypi
|
||||||
path: extern/
|
path: extern/meta-raspberrypi
|
||||||
tag: scarthgap
|
tag: scarthgap
|
||||||
poky:
|
poky:
|
||||||
url: http://git.yoctoproject.org/git/poky
|
url: https://git.yoctoproject.org/git/poky
|
||||||
tag: scarthgap
|
tag: scarthgap
|
||||||
path: extern/
|
path: extern/poky
|
||||||
layers:
|
layers:
|
||||||
meta:
|
meta:
|
||||||
meta-poky:
|
meta-poky:
|
||||||
meta-yocto-bsp:
|
meta-yocto-bsp:
|
||||||
meta-openembedded:
|
meta-openembedded:
|
||||||
url: git://git.openembedded.org/meta-openembedded
|
url: https://git.openembedded.org/meta-openembedded
|
||||||
tag: scarthgap
|
tag: scarthgap
|
||||||
path: extern/
|
path: extern/meta-openembedded
|
||||||
layers:
|
layers:
|
||||||
meta-oe:
|
meta-oe:
|
||||||
meta-multimedia:
|
meta-multimedia:
|
||||||
@@ -34,6 +34,8 @@ repos:
|
|||||||
meta-python:
|
meta-python:
|
||||||
local:
|
local:
|
||||||
path: meta-rpilinux
|
path: meta-rpilinux
|
||||||
|
local:
|
||||||
|
path: meta-rpilinux
|
||||||
|
|
||||||
local_conf_header:
|
local_conf_header:
|
||||||
ipk: |
|
ipk: |
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
name: build-image
|
||||||
|
description: >-
|
||||||
|
Build a kas project in a persistent directory and expose the flashable
|
||||||
|
images. The workspace is synced into the build dir instead of built in
|
||||||
|
place, because checkout cleans ignored files (build/, extern/) and bitbake
|
||||||
|
reuse depends on sstate-cache surviving between runs.
|
||||||
|
inputs:
|
||||||
|
project:
|
||||||
|
description: kas configuration file, relative to the repository
|
||||||
|
required: false
|
||||||
|
default: .config.yaml
|
||||||
|
machine:
|
||||||
|
description: Machine the images are deployed for
|
||||||
|
required: false
|
||||||
|
default: raspberrypi4-64
|
||||||
|
image-prefix:
|
||||||
|
description: Artifact filename prefix in the deploy dir
|
||||||
|
required: false
|
||||||
|
default: rpilinux-image
|
||||||
|
skip-build:
|
||||||
|
description: Set to true to only collect images from an existing build dir
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
outputs:
|
||||||
|
build-dir:
|
||||||
|
description: Persistent project directory on the runner
|
||||||
|
value: ${{ steps.locate.outputs.build-dir }}
|
||||||
|
image-dir:
|
||||||
|
description: Deploy dir bitbake wrote to
|
||||||
|
value: ${{ steps.locate.outputs.image-dir }}
|
||||||
|
images:
|
||||||
|
description: Newline separated artifact filenames
|
||||||
|
value: ${{ steps.collect.outputs.images }}
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Sync workspace into persistent build dir
|
||||||
|
if: ${{ inputs.skip-build != 'true' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SRC="$(pwd)"
|
||||||
|
DST="${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
|
||||||
|
# checkout deletes ignored files, so the build tree must live elsewhere
|
||||||
|
if [ "$SRC" = "$DST" ]; then
|
||||||
|
echo "::error::workspace == build dir ($SRC); point YOCTO_HOME somewhere else"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "syncing $SRC -> $DST"
|
||||||
|
mkdir -p "$DST"
|
||||||
|
# .git stays (kas requires a repo); extern/ + build/ only exist in DST,
|
||||||
|
# so --delete cannot reach them
|
||||||
|
rsync -a --delete --exclude /build/ --exclude /extern/ ./ "$DST/"
|
||||||
|
|
||||||
|
- name: kas build
|
||||||
|
if: ${{ inputs.skip-build != 'true' }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd "${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
|
||||||
|
git config --global --add safe.directory "$PWD" || true
|
||||||
|
"${YOCTO_HOME:-$HOME/yocto-build}/venv/bin/kas" build
|
||||||
|
|
||||||
|
- name: Locate deploy dir
|
||||||
|
id: locate
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
BUILD_DIR="${YOCTO_HOME:-$HOME/yocto-build}/$(basename "$GITHUB_REPOSITORY")"
|
||||||
|
IMAGE_DIR="$(find "$BUILD_DIR/build" -type d -path "*/tmp/deploy/images/${{ inputs.machine }}" | head -1)"
|
||||||
|
if [ -z "$IMAGE_DIR" ]; then
|
||||||
|
echo "::error::no deploy dir for ${{ inputs.machine }} under $BUILD_DIR/build"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "build-dir=$BUILD_DIR" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "image-dir=$IMAGE_DIR" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Collect artifacts
|
||||||
|
id: collect
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
IMAGE_DIR="${{ steps.locate.outputs.image-dir }}"
|
||||||
|
shopt -s nullglob
|
||||||
|
# only the timestamped names: the short ones are hardlinks to the same
|
||||||
|
# inode and would upload every image twice.
|
||||||
|
# NOTE: the glob must stay OUTSIDE the quotes - "$DIR/pat*" never expands.
|
||||||
|
FILES=("${IMAGE_DIR}/${{ inputs.image-prefix }}."*.rootfs-[0-9]*.wic.* \
|
||||||
|
"${IMAGE_DIR}/${{ inputs.image-prefix }}-"*.rootfs-[0-9]*.wic.*)
|
||||||
|
if [ "${#FILES[@]}" -eq 0 ]; then
|
||||||
|
echo "::error::no flashable .wic image for ${{ inputs.machine }} in $IMAGE_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p dist
|
||||||
|
cp -v "${FILES[@]}" dist/
|
||||||
|
( cd dist && sha256sum * > SHA256SUMS )
|
||||||
|
ls -lh dist
|
||||||
|
printf 'images=%s\n' "$(ls dist | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Summarise
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
SUMMARY="${GITHUB_STEP_SUMMARY:-}"
|
||||||
|
[ -n "$SUMMARY" ] || exit 0
|
||||||
|
{
|
||||||
|
echo "## ${{ inputs.image-prefix }} for ${{ inputs.machine }}"
|
||||||
|
echo
|
||||||
|
echo "| artifact | sha256 |"
|
||||||
|
echo "| --- | --- |"
|
||||||
|
( cd dist && while read -r sum file; do
|
||||||
|
[ "$file" = SHA256SUMS ] || echo "| \`$file\` | \`${sum:0:16}\` |"
|
||||||
|
done < SHA256SUMS )
|
||||||
|
echo
|
||||||
|
echo "Flash with \`bzcat <image> | sudo dd of=/dev/sdX bs=4M status=progress\`."
|
||||||
|
echo
|
||||||
|
echo "Incremental build tree: \`${{ steps.locate.outputs.build-dir }}\`"
|
||||||
|
} >> "$SUMMARY"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: check-build-host
|
||||||
|
description: >-
|
||||||
|
Report the build host and fail fast when executables, CPU or disk that the
|
||||||
|
build needs are missing - a mistake should cost seconds, not hours.
|
||||||
|
inputs:
|
||||||
|
tools:
|
||||||
|
description: Space separated executables that must be on PATH
|
||||||
|
required: false
|
||||||
|
default: git python3
|
||||||
|
min-disk-gb:
|
||||||
|
description: Warn when the build filesystem has less than this much free
|
||||||
|
required: false
|
||||||
|
default: "100"
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Host report
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "os : $(. /etc/os-release && echo "$PRETTY_NAME") ($(uname -m), $(uname -r))"
|
||||||
|
echo "user : $(id -un) home=$HOME"
|
||||||
|
echo "cpu : $(nproc) cores / $(awk '/MemTotal/{printf "%.0fG", $2/1048576}' /proc/meminfo) ram"
|
||||||
|
df -h . | tail -1 | sed 's/^/disk : /'
|
||||||
|
missing=""
|
||||||
|
for tool in ${{ inputs.tools }}; do
|
||||||
|
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
|
||||||
|
done
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo "::error::Host is missing:$missing"
|
||||||
|
echo "Install them on the runner host, e.g. sudo apt-get install -y $missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
free_gb="$(df --output=avail -BG . | tail -1 | tr -d ' ')"
|
||||||
|
if [ "$free_gb" -lt "${{ inputs.min-disk-gb }}" ]; then
|
||||||
|
echo "::warning::only ${free_gb}G free on $(df --output=target . | tail -1); expected >= ${{ inputs.min-disk-gb }}G"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: setup-kas
|
||||||
|
description: Create or reuse the persistent python venv that holds kas.
|
||||||
|
inputs:
|
||||||
|
requirement:
|
||||||
|
description: pip requirement for kas (pinned, e.g. kas==5.5)
|
||||||
|
required: false
|
||||||
|
default: kas
|
||||||
|
outputs:
|
||||||
|
kas:
|
||||||
|
description: Absolute path of the kas executable
|
||||||
|
value: ${{ steps.venv.outputs.kas }}
|
||||||
|
reused:
|
||||||
|
description: "true when the venv already existed (warm run)"
|
||||||
|
value: ${{ steps.venv.outputs.reused }}
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Prepare venv
|
||||||
|
id: venv
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
BUILD_ROOT="${YOCTO_HOME:-$HOME/yocto-build}"
|
||||||
|
VENV="$BUILD_ROOT/venv"
|
||||||
|
mkdir -p "$BUILD_ROOT"
|
||||||
|
if [ -d "$VENV" ]; then
|
||||||
|
echo "reused=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "reused=false" >> "$GITHUB_OUTPUT"
|
||||||
|
python3 -m venv "$VENV"
|
||||||
|
fi
|
||||||
|
"$VENV/bin/pip" install --quiet --upgrade "${{ inputs.requirement }}"
|
||||||
|
echo "kas=$VENV/bin/kas" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "kas $($VENV/bin/kas --version) in $VENV"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Build RPi image
|
||||||
|
|
||||||
|
# Yocto (kas) build of rpilinux-image. Steps live in .gitea/actions/* so this
|
||||||
|
# file stays a description of the pipeline, not a shell script.
|
||||||
|
#
|
||||||
|
# Host-specific paths come from repo settings (Settings -> Actions ->
|
||||||
|
# Variables), so the workflow is not tied to one machine:
|
||||||
|
# YOCTO_HOME persistent build dir on the runner
|
||||||
|
# Order of precedence for the build dir: this variable, then the runner's
|
||||||
|
# own YOCTO_HOME (runner.envs), then $HOME/yocto-build.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# main is release protected, ci/* branches are free scratch space
|
||||||
|
branches: [main, 'ci/*']
|
||||||
|
pull_request:
|
||||||
|
# gates merges into main and posts the commit status that branch
|
||||||
|
# protection waits for
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
skip-build:
|
||||||
|
description: Collect images from the existing build dir instead of rebuilding
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: yocto-rpi-build
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
YOCTO_HOME: ${{ vars.YOCTO_HOME }}
|
||||||
|
MACHINE: raspberrypi4-64
|
||||||
|
IMAGE_PREFIX: rpilinux-image
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: yocto
|
||||||
|
timeout-minutes: 600
|
||||||
|
steps:
|
||||||
|
- name: Check out sources
|
||||||
|
uses: builtin:checkout
|
||||||
|
|
||||||
|
- name: Verify build host
|
||||||
|
uses: $/.gitea/actions/check-build-host
|
||||||
|
with:
|
||||||
|
tools: git python3 curl wget rsync gawk xz diffstat chrpath bison flex node
|
||||||
|
min-disk-gb: "200"
|
||||||
|
|
||||||
|
- name: Set up kas
|
||||||
|
id: kas
|
||||||
|
uses: $/.gitea/actions/setup-kas
|
||||||
|
with:
|
||||||
|
requirement: kas==5.5
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
id: image
|
||||||
|
uses: $/.gitea/actions/build-image
|
||||||
|
with:
|
||||||
|
machine: ${{ env.MACHINE }}
|
||||||
|
image-prefix: ${{ env.IMAGE_PREFIX }}
|
||||||
|
skip-build: ${{ github.event.inputs.skip-build }}
|
||||||
|
|
||||||
|
- name: Upload image
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ env.IMAGE_PREFIX }}-${{ env.MACHINE }}
|
||||||
|
path: dist/
|
||||||
|
retention-days: 14
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: Sandbox - cmake + ccache on the pool
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ci/sandbox]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Inside the workspace so actions/cache can store it. The path is identical on
|
||||||
|
# every run, which matters because ccache hashes the compiler command line.
|
||||||
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: cmake
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: sandbox_c
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Toolchain
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
command -v cmake >/dev/null && command -v ccache >/dev/null && exit 0
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq --no-install-recommends \
|
||||||
|
cmake ninja-build gcc libc6-dev ccache ca-certificates >/dev/null
|
||||||
|
cmake --version | head -1
|
||||||
|
ccache --version | head -1
|
||||||
|
|
||||||
|
# Cache the compiler cache, never build/: CMake bakes absolute paths into
|
||||||
|
# CMakeCache.txt, so a restored build tree breaks if the workspace moves.
|
||||||
|
- name: Cache ccache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.CCACHE_DIR }}
|
||||||
|
key: ccache-${{ hashFiles('sandbox_c/**') }}
|
||||||
|
restore-keys: ccache-
|
||||||
|
|
||||||
|
- name: ccache before build
|
||||||
|
run: ccache --show-stats | grep -E "cache (hits|misses|read)" || ccache --show-stats
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -G Ninja \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build build --parallel
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
|
- name: ccache after build
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
ccache --show-stats
|
||||||
|
set -o pipefail
|
||||||
|
ctest --test-dir build 2>&1 | grep -E "tests passed"
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
name: Sandbox - ephemeral runner + shared cache
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ci/sandbox]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cargo:
|
||||||
|
runs-on: sandbox
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: sandbox
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Where am I?
|
||||||
|
run: |
|
||||||
|
echo "runner : ${RUNNER_NAME}"
|
||||||
|
echo "uid : $(id -u) cgroup: $(cut -d/ -f3 /proc/self/cgroup 2>/dev/null || echo n/a)"
|
||||||
|
echo "cache : ${ACTIONS_CACHE_URL:-unset}"
|
||||||
|
|
||||||
|
# Restore first, so a warm cache also skips the toolchain download.
|
||||||
|
- name: Cache rust toolchain, registry and target
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo
|
||||||
|
~/.rustup
|
||||||
|
sandbox/target
|
||||||
|
key: cargo-${{ hashFiles('sandbox/Cargo.lock') }}
|
||||||
|
restore-keys: cargo-
|
||||||
|
|
||||||
|
- name: Toolchain
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if ! command -v cargo >/dev/null && [ ! -x "$HOME/.cargo/bin/cargo" ]; then
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
||||||
|
fi
|
||||||
|
# runner v5.2.2 does not apply GITHUB_PATH to later steps, so use absolute paths
|
||||||
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
"$HOME/.cargo/bin/cargo" --version
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
set -o pipefail # without this a failing build looks green
|
||||||
|
CARGO="$HOME/.cargo/bin/cargo"
|
||||||
|
echo "HOME=$HOME cargo there: $([ -x "$CARGO" ] && echo yes || echo no)"
|
||||||
|
"$CARGO" test 2>&1 | tail -22
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Folder-specific settings
|
||||||
|
//
|
||||||
|
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||||
|
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||||
|
{}
|
||||||
Generated
+214
@@ -0,0 +1,214 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ci-sandbox"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.191"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.154"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "ci-sandbox"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
anyhow = "1"
|
||||||
|
rand = "0.8"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//! Tiny crate used to exercise CI caching: enough deps to make a warm
|
||||||
|
//! build obviously faster than a cold one.
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Reading {
|
||||||
|
pub sensor: String,
|
||||||
|
pub celsius: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_fahrenheit(celsius: f64) -> f64 {
|
||||||
|
celsius * 9.0 / 5.0 + 32.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(json: &str) -> Result<Reading> {
|
||||||
|
let r: Reading = serde_json::from_str(json)?;
|
||||||
|
if r.celsius < -273.15 {
|
||||||
|
bail!("{} C is below absolute zero", r.celsius);
|
||||||
|
}
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jitter(amount: f64) -> f64 {
|
||||||
|
use rand::Rng;
|
||||||
|
rand::thread_rng().gen_range(-amount..=amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_units() {
|
||||||
|
assert!((to_fahrenheit(0.0) - 32.0).abs() < 1e-9);
|
||||||
|
assert!((to_fahrenheit(100.0) - 212.0).abs() < 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_reading() {
|
||||||
|
let r = parse(r#"{"sensor":"attic","celsius":21.5}"#).unwrap();
|
||||||
|
assert_eq!(r.sensor, "attic");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_impossible_temperature() {
|
||||||
|
assert!(parse(r#"{"sensor":"x","celsius":-300.0}"#).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/build
|
||||||
|
/.ccache
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles/
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22)
|
||||||
|
project(sandbox_c C)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
|
add_library(tempsense STATIC src/temperature.c src/convert.c src/parse.c src/format.c)
|
||||||
|
target_include_directories(tempsense PUBLIC include)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_executable(test_tempsense tests/test_temperature.c)
|
||||||
|
target_link_libraries(test_tempsense PRIVATE tempsense)
|
||||||
|
add_test(NAME tempsense COMMAND test_tempsense)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef TEMPSENSE_H
|
||||||
|
#define TEMPSENSE_H
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct { char sensor[32]; double celsius; } Reading;
|
||||||
|
|
||||||
|
double to_fahrenheit(double celsius);
|
||||||
|
int parse_reading(const char *json, Reading *out); /* 0 on success */
|
||||||
|
int format_reading(const Reading *in, char *buf, size_t len);
|
||||||
|
double clamp_to_absolute_zero(double celsius);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include "temperature.h"
|
||||||
|
double to_fahrenheit(double celsius) { return celsius * 9.0 / 5.0 + 32.0; }
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#include "temperature.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
int format_reading(const Reading *in, char *buf, size_t len) {
|
||||||
|
if (!in || !buf || len < 16) return 1;
|
||||||
|
int n = snprintf(buf, len, "{\"sensor\":\"%s\",\"celsius\":%.2f}", in->sensor, in->celsius);
|
||||||
|
return (n < 0 || (size_t)n >= len) ? 2 : 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#include "temperature.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Deliberately tiny parser: finds "celsius":<num> and an optional
|
||||||
|
"sensor":"..." so the test can assert on both fields. */
|
||||||
|
int parse_reading(const char *json, Reading *out) {
|
||||||
|
if (!json || !out) return 1;
|
||||||
|
const char *c = strstr(json, "\"celsius\"");
|
||||||
|
const char *s = strstr(json, "\"sensor\"");
|
||||||
|
if (!c) return 2;
|
||||||
|
c = strchr(c, ':');
|
||||||
|
if (!c) return 3;
|
||||||
|
char *end = NULL;
|
||||||
|
out->celsius = strtod(c + 1, &end);
|
||||||
|
if (end == c + 1) return 4;
|
||||||
|
out->sensor[0] = '\0';
|
||||||
|
if (s) {
|
||||||
|
s = strchr(s, ':');
|
||||||
|
if (s) {
|
||||||
|
const char *open = strchr(s, '"');
|
||||||
|
if (open) {
|
||||||
|
const char *close = strchr(open + 1, '"');
|
||||||
|
if (close) {
|
||||||
|
size_t n = (size_t)(close - open - 1);
|
||||||
|
if (n >= sizeof out->sensor) n = sizeof out->sensor - 1;
|
||||||
|
memcpy(out->sensor, open + 1, n);
|
||||||
|
out->sensor[n] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "temperature.h"
|
||||||
|
#define ABSOLUTE_ZERO_C (-273.15)
|
||||||
|
double clamp_to_absolute_zero(double celsius) { return celsius < ABSOLUTE_ZERO_C ? ABSOLUTE_ZERO_C : celsius; }
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include "temperature.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
assert(fabs(to_fahrenheit(0.0) - 32.0) < 1e-9);
|
||||||
|
assert(fabs(to_fahrenheit(100.0) - 212.0) < 1e-9);
|
||||||
|
|
||||||
|
Reading r;
|
||||||
|
assert(parse_reading("{\"sensor\":\"attic\",\"celsius\":21.50}", &r) == 0);
|
||||||
|
assert(strcmp(r.sensor, "attic") == 0);
|
||||||
|
assert(fabs(r.celsius - 21.5) < 1e-9);
|
||||||
|
assert(parse_reading("{\"celsius\":-300.0}", &r) == 0);
|
||||||
|
assert(fabs(clamp_to_absolute_zero(r.celsius) + 273.15) < 1e-9);
|
||||||
|
assert(parse_reading("nonsense", &r) != 0);
|
||||||
|
|
||||||
|
char buf[128];
|
||||||
|
Reading in = { "boiler", 65.25 };
|
||||||
|
assert(format_reading(&in, buf, sizeof buf) == 0);
|
||||||
|
assert(strstr(buf, "\"sensor\":\"boiler\"") != NULL);
|
||||||
|
|
||||||
|
puts("all tempsense assertions passed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user