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
34 changes: 34 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
# clang-format has no dedicated "C" language key: plain C is formatted under
# the Cpp rules. `make format` applies this; `make format-check` enforces it.
Language: Cpp
BasedOnStyle: LLVM

IndentWidth: 2
TabWidth: 2
UseTab: Never
ColumnLimit: 100

PointerAlignment: Right
AlignAfterOpenBracket: BlockIndent
AllowAllArgumentsOnNextLine: true
BinPackArguments: false
BinPackParameters: false

AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never

SpaceBeforeParens: ControlStatements
InsertBraces: true
InsertNewlineAtEOF: true

SortIncludes: CaseInsensitive
IncludeBlocks: Preserve

# C23 attributes such as [[nodiscard]] introduce a statement, so keep
# clang-format from gluing them onto the following declaration.
AttributeMacros:
- LINKEDLIST_REPRODUCIBLE
- LINKEDLIST_UNSEQUENCED
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Editor-agnostic baseline. VS Code honours this through the EditorConfig
# extension; clang-format owns the finer details of C formatting.
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[Makefile]
indent_style = tab

[*.md]
trim_trailing_whitespace = false
180 changes: 135 additions & 45 deletions .github/workflows/c-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,154 @@ name: C CI

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]
workflow_dispatch:

# A new push to the same branch makes the previous run pointless.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
format:
name: clang-format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"

# Pinned to an exact version on purpose. clang-format's output changes
# between major releases, so an unpinned binary makes this check depend
# on whatever the runner happens to ship -- CI ran 18 while the tree had
# been formatted with 22, and the two disagreed. The pip package is the
# same binary on every platform, so contributors can match it exactly:
# pipx install clang-format==22.1.8
- name: Install a pinned clang-format
run: pip install clang-format==22.1.8

- name: Check formatting
run: make format-check

test:
name: test (${{ matrix.os }}, ${{ matrix.cc }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# GCC 14 is the oldest GCC that builds this. Note it still reports
# __STDC_VERSION__ as the draft 202000L under -std=c23 -- GCC only
# bumped the macro in 15 -- which the header's guard allows for.
- os: ubuntu-latest
cc: gcc-14
setup: sudo apt-get update -y && sudo apt-get install -y gcc-14
# Clang 19, not 18. Clang 18 accepts -std=c23 but has no C23
# `constexpr`, which the test suite uses. It is not in the Ubuntu
# 24.04 archive, hence apt.llvm.org.
- os: ubuntu-latest
cc: clang-19
setup: |
wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh
chmod +x /tmp/llvm.sh
sudo /tmp/llvm.sh 19
# Apple Clang has C23 but not [[reproducible]] / [[unsequenced]],
# so this leg is what proves the __has_c_attribute guards in
# include/linkedlist.h actually work.
- os: macos-latest
cc: cc
setup: ""
steps:
- uses: actions/checkout@v4

- name: Install compiler
if: matrix.setup != ''
run: ${{ matrix.setup }}

- name: Check versions
run: |
${{ matrix.cc }} --version
make --version | head -1

- name: Build libraries
run: make build CC=${{ matrix.cc }}

- name: Run tests
run: make test CC=${{ matrix.cc }}

- name: Run tests under ASan + UBSan
run: make sanitize CC=${{ matrix.cc }}

# Confirms the header still compiles for a consumer that only has the
# installed copy -- no ../include/ relative paths sneaking back in.
- name: Check install / uninstall
run: |
make install CC=${{ matrix.cc }} PREFIX="$PWD/staging"
test -f staging/include/linkedlist.h
make uninstall PREFIX="$PWD/staging"

cross:
name: cross-compile (aarch64-linux-gnu)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt-get update -y
sudo apt-get install -y gcc-13 valgrind
# gcc-14-* rather than the unversioned gcc-*: the default cross compiler
# on ubuntu-24.04 is GCC 13, which predates -std=c23.
- name: Install cross toolchain and emulator
run: |
sudo apt-get update -y
sudo apt-get install -y gcc-14-aarch64-linux-gnu qemu-user-static

- name: Set up gcc-13
run: |
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 90
# CROSS_COMPILE supplies the ar prefix; CC overrides just the compiler,
# because the package installs a version-suffixed binary.
- name: Show resolved toolchain
run: make toolchain CROSS_COMPILE=aarch64-linux-gnu- CC=aarch64-linux-gnu-gcc-14

- name: Check versions
run: |
gcc --version
make --version
- name: Build for aarch64
run: make build CROSS_COMPILE=aarch64-linux-gnu- CC=aarch64-linux-gnu-gcc-14

- name: make build
run: make build
# Proves the target detection actually took effect: a .so with an ELF
# aarch64 header, not a host-shaped artifact with the wrong name.
- name: Verify the artifacts are aarch64 ELF
run: |
file build/lib/liblinkedlist.so build/lib/liblinkedlist.a
file build/lib/liblinkedlist.so | grep -q 'ELF 64-bit LSB shared object, ARM aarch64'

- name: make test
run: make test
- name: Run the test suite under qemu
run: |
make test CROSS_COMPILE=aarch64-linux-gnu- CC=aarch64-linux-gnu-gcc-14 \
RUNNER="qemu-aarch64-static -L /usr/aarch64-linux-gnu"

- name: make memcheck
run: make memcheck
memcheck:
name: valgrind
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: sudo apt-get update -y && sudo apt-get install -y gcc-14 valgrind
# Deliberately a plain build, not a sanitizer one: valgrind and ASan
# both hook the allocator and refuse to run together.
- name: Run tests under valgrind
run: make memcheck CC=gcc-14

build:
docs:
name: doxygen
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt-get update -y
sudo apt-get install -y gcc-13 valgrind

- name: Set up gcc-13
run: |
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 90

- name: Check versions
run: |
gcc --version
make --version

- name: make build
run: make build
- uses: actions/checkout@v4
- name: Install doxygen
run: sudo apt-get update -y && sudo apt-get install -y doxygen
- name: Build API docs
run: make docs
- uses: actions/upload-artifact@v4
with:
name: api-docs
path: build/docs/html
retention-days: 7
54 changes: 19 additions & 35 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,63 +1,47 @@
# Prerequisites
*.d
# Everything the Makefile produces -- objects, .d dependency files, both
# libraries, the test binary, the sanitizer tree, and the Doxygen HTML -- goes
# under build/, so one entry covers the lot.
build/

# Debug symbol bundles produced by -g on macOS.
*.dSYM/

# clangd's --background-index writes its index here.
.cache/

# If you generate this with `bear -- make`, it holds absolute paths specific to
# one machine, so it should not be committed. compile_flags.txt is the checked-in
# alternative.
compile_commands.json

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
# Precompiled headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo
*.lib

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
*.dll

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

# Extra files and directories
lib/
obj/
build/
html/
latex/

# macOS Finder metadata
.DS_Store
._.DS_Store
**/.DS_Store
Expand Down
43 changes: 24 additions & 19 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
{
// IntelliSense here is disabled in settings.json -- clangd does that job.
// This file still matters because the cpptools debugger reads it, and it
// keeps things working for anyone who turns the cpptools engine back on.
//
// "cStandard": "c23" needs cpptools 1.19+. Older releases only know "c17"
// and will quietly downgrade, which brings back the phantom errors on
// nullptr and [[nodiscard]].
"configurations": [
{
"name": "macos-gcc-arm64",
"compilerPath": "/opt/homebrew/bin/gcc-13",
// Adjust to whatever `ls /opt/homebrew/bin/gcc-*` reports. The Makefile
// picks the newest one automatically; this path has to be spelled out.
"compilerPath": "/opt/homebrew/bin/gcc-16",
"compilerArgs": ["-std=c23"],
"intelliSenseMode": "macos-gcc-arm64",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/include/**",
"/opt/homebrew/lib/gcc/13/**"
],
"cStandard": "c23",
"cppStandard": "c++23",
"includePath": ["${workspaceFolder}/include/**", "${workspaceFolder}/src/**"],
"defines": [],
"macFrameworkPath": [
"${workspaceFolder}/**",
"/System/Library/Frameworks"
],
"macFrameworkPath": ["/System/Library/Frameworks"]
},
{
"name": "linux-gcc-x64",
"compilerPath": "/usr/bin/gcc",
"compilerArgs": ["-std=c23"],
"intelliSenseMode": "linux-gcc-x64",
"cStandard": "c23",
"cppStandard": "c++23",
"configurationProvider": "ms-vscode.makefile-tools",
"browse": {
"path": [
"${workspaceFolder}/**",
"${workspaceFolder}/include/**",
"/opt/homebrew/lib/gcc/13/**"
]
}
"includePath": ["${workspaceFolder}/include/**", "${workspaceFolder}/src/**"],
"defines": []
}
],
"version": 4
}
}
Loading
Loading