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
59 changes: 52 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,120 +15,140 @@ jobs:
matrix:
include:
# ── Debian stable ──────────────────────────────────────────────────
# static: true — libgit2-dev ships libgit2.a on Debian
- os: debian:stable
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Release
static: true

- os: debian:stable
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Debug
static: true

- os: debian:stable
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Release
static: true

- os: debian:stable
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Debug
static: true

# ── Ubuntu LTS (24.04) ─────────────────────────────────────────────
# static: true — libgit2-dev ships libgit2.a on Ubuntu
- os: ubuntu:24.04
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Release
static: true

- os: ubuntu:24.04
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Debug
static: true

- os: ubuntu:24.04
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Release
static: true

- os: ubuntu:24.04
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Debug
static: true

# ── Fedora (latest) ───────────────────────────────────────────────────
# static: false — Fedora does not ship libgit2.a / libssh2.a / libssl.a
- os: fedora:latest
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Release
static: false

- os: fedora:latest
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Debug
static: false

- os: fedora:latest
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Release
static: false

- os: fedora:latest
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Debug
static: false

# ── Arch Linux (stable) ───────────────────────────────────────────────
# static: false — Arch does not ship libgit2.a
- os: archlinux:latest
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Release
static: false

- os: archlinux:latest
runner: ubuntu-latest
compiler: gcc
cc: gcc
cxx: g++
build_type: Debug
static: false

- os: archlinux:latest
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Release
static: false

- os: archlinux:latest
runner: ubuntu-latest
compiler: clang
cc: clang
cxx: clang++
build_type: Debug
static: false

runs-on: ${{ matrix.runner }}

Expand All @@ -147,7 +167,7 @@ jobs:
- name: Cache CMake FetchContent (spdlog, clipp, fmt)
uses: actions/cache@v5
with:
path: build/_deps
path: build-so/_deps
key: fetchcontent-${{ matrix.os }}-${{ matrix.compiler }}-${{ hashFiles('CMakeLists.txt') }}
restore-keys: |
fetchcontent-${{ matrix.os }}-${{ matrix.compiler }}-
Expand All @@ -159,23 +179,46 @@ jobs:
git config --global user.name "GitHub Actions"
git config --global init.defaultBranch master

- name: Build
- name: Build dynamic
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
CI: "1"
run: make BUILD=build-so TYPE=${{ matrix.build_type }}

- name: Test dynamic
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
CI: "1"
run: make BUILD=build-so TYPE=${{ matrix.build_type }} test

- name: Install static dependencies
if: matrix.static
env:
DEBIAN_FRONTEND: noninteractive
run: bash dependencies.sh --compiler=${{ matrix.compiler }} --static

- name: Build static
if: matrix.static
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
CI: "1"
run: make TYPE=${{ matrix.build_type }}
run: make BUILD=build-a STATIC=1 TYPE=${{ matrix.build_type }}

- name: Test
- name: Test static
if: matrix.static
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
CI: "1"
run: make TYPE=${{ matrix.build_type }} test
run: make BUILD=build-a STATIC=1 TYPE=${{ matrix.build_type }} test

- name: Compute artifact name
if: failure()
id: artifact-name
shell: bash
run: |
# Colons and slashes are not allowed in artifact names; replace with dashes.
raw="test-artifacts-${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.build_type }}"
Expand All @@ -187,8 +230,10 @@ jobs:
with:
name: ${{ steps.artifact-name.outputs.name }}
path: |
build/test/
build/Testing/
build-so/test/
build-so/Testing/
build-a/test/
build-a/Testing/
retention-days: 7

coverage:
Expand Down
43 changes: 42 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

option(WIP_COVERAGE "Enable code coverage instrumentation" OFF)
option(WIP_STATIC "Build a fully static binary" OFF)

include(FetchContent)
include(CheckCXXSourceCompiles)
Expand Down Expand Up @@ -51,7 +52,47 @@ endif()

include(FindPkgConfig)

pkg_check_modules(LIBGIT2 REQUIRED libgit2)
if(WIP_STATIC)
message(STATUS "WIP_STATIC=ON — building a mostly-static binary (libgit2 and all its deps statically linked; glibc stays shared)")
pkg_check_modules(LIBGIT2 REQUIRED libgit2)

# Collect the full transitive static link flags from pkg-config.
# We split them into two groups so CMake can handle each token cleanly:
# LIBGIT2_STATIC_LIBS — -lfoo tokens (library names, no -L prefix)
# LIBGIT2_STATIC_DIRS — -L/path tokens (search paths)
execute_process(
COMMAND pkg-config --static --libs libgit2
OUTPUT_VARIABLE _git2_raw
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND pkg-config --static --libs-only-l libgit2
OUTPUT_VARIABLE _git2_libs_raw
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND pkg-config --static --libs-only-L libgit2
OUTPUT_VARIABLE _git2_dirs_raw
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Also pull in the transitive deps of libssh2 (uses OpenSSL on Debian)
execute_process(
COMMAND pkg-config --static --libs-only-l libssh2
OUTPUT_VARIABLE _ssh2_libs_raw
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "-l" "" _git2_libs_stripped "${_git2_libs_raw}")
string(REPLACE "-L" "" _git2_dirs_stripped "${_git2_dirs_raw}")
string(REPLACE "-l" "" _ssh2_libs_stripped "${_ssh2_libs_raw}")
separate_arguments(LIBGIT2_STATIC_LIBS UNIX_COMMAND "${_git2_libs_stripped}")
separate_arguments(LIBGIT2_STATIC_DIRS UNIX_COMMAND "${_git2_dirs_stripped}")
separate_arguments(SSH2_STATIC_LIBS UNIX_COMMAND "${_ssh2_libs_stripped}")
# Deduplicate (SSH2 libs are a subset)
list(APPEND LIBGIT2_STATIC_LIBS ${SSH2_STATIC_LIBS})
list(REMOVE_DUPLICATES LIBGIT2_STATIC_LIBS)
else()
pkg_check_modules(LIBGIT2 REQUIRED libgit2)
endif()

# Optional: GTest/GMock for unit tests (only available on some distros)
find_package(GTest QUIET)
Expand Down
24 changes: 14 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,25 @@ NPROC ?= $(shell nproc || echo 1)
CC ?= $(shell which clang gcc cc | head -n1)
CXX ?= $(shell which clang g++ c++ | head -n1)
COVERAGE ?= false
STATIC ?= 0
# Locate the right gcov-compatible tool to match the compiler:
# - if CC is clang, use llvm-cov (prefer plain symlink, fall back to versioned)
# - otherwise use plain gcov
# Use = (recursive) not := (immediate) so CC override on the command line is respected.
_IS_CLANG = $(shell $(CC) --version 2>/dev/null | grep -c clang)
GCOV_TOOL = $(if $(filter 1,$(_IS_CLANG)),$(shell which llvm-cov 2>/dev/null || ls /usr/bin/llvm-cov-* 2>/dev/null | sort -V | tail -1) gcov,gcov)
$(info ## TYPE=${TYPE} CC=${CC} CXX=${CXX} COVERAGE=${COVERAGE})
$(info ## TYPE=${TYPE} CC=${CC} CXX=${CXX} COVERAGE=${COVERAGE} STATIC=${STATIC})

# Coverage flag for CMake
COVERAGE_FLAG = $(if $(filter 1 yes true YES TRUE,${COVERAGE}),-DWIP_COVERAGE=ON,)

# Static flag for CMake
STATIC_FLAG = $(if $(filter 1 yes true YES TRUE,${STATIC}),-DWIP_STATIC=ON,)

GIT_WIP = ${BUILD}/src/git-wip

all: ## [default] build the project (uses TYPE={Release,Debug}, COVERAGE={true,false})
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG}
all: ## [default] build the project (uses TYPE={Release,Debug}, COVERAGE={0,1}, STATIC={0,1})
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG} ${STATIC_FLAG}
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}ln -fs "${BUILD}"/compile_commands.json compile_commands.json
${Q}ln -fs "${GIT_WIP}" .
Expand All @@ -60,14 +64,14 @@ distclean: ## remove build directory completely
help:
${Q}python3 -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)

test: ## run unit tests (with ctest, uses REBUILD={true,false}, COVERAGE={true,false})
test: ## run unit tests (with ctest, uses REBUILD={0,1}, COVERAGE={0,1}, STATIC={0,1})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG}
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG} ${STATIC_FLAG}
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" $(if ${CI},--output-on-failure -VV)
${Q}echo " ✅ Unit tests complete."

coverage: ## check code coverage (with lcov, uses REBUILD={true,false})
coverage: ## check code coverage (with lcov, uses REBUILD={0,1})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" -DWIP_COVERAGE=ON -DCMAKE_C_COMPILER="${CC}" -DCMAKE_CXX_COMPILER="${CXX}"
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
Expand Down Expand Up @@ -97,15 +101,15 @@ coverage: ## check code coverage (with lcov, uses REBUILD={true,false})
--ignore-errors category
${Q}echo " ✅ Coverage report generated in coverage-report/"

install: ## install the package (to the `PREFIX`, uses REBUILD={true,false})
install: ## install the package (to the `PREFIX`, uses REBUILD={0,1}, STATIC={0,1})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}"
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${STATIC_FLAG}
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}${CMAKE} --build "${BUILD}" --target install --config "${TYPE}"

format: ## format the project sources (uses REBUILD={true,false})
format: ## format the project sources (uses REBUILD={0,1})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}"
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${STATIC_FLAG}
${Q}${CMAKE} --build "${BUILD}" --target clang-format

full-test: ## like test, but with a full rebuild
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@ Dependencies (`spdlog`) are fetched automatically by CMake via
`FetchContent`. `libgit2` must be installed system-wide (e.g.
`apt install libgit2-dev`).

If you'd rather build a static binary (tested on Debian/Ubuntu), you can use:

```sh
$ ./dependencies.sh --static # install extra dependencies
$ make STATIC=1 # build the static binary
```

---

## Installation
Expand Down
Loading
Loading