Skip to content
This repository was archived by the owner on May 1, 2026. It is now read-only.
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
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Force LF line endings on configure_file templates so substitution
# output is byte-identical regardless of which platform the
# checkout happened on -- otherwise CRLF on Windows runners would
# leak into the generated header and break reproducible-build
# comparisons. Every other file is left to the global git config.
*.h.in text eol=lf
40 changes: 40 additions & 0 deletions .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ jobs:
DESTDIR="$PWD/staging" meson install -C builddir
# Architecture-independent paths are stable.
test -f staging/usr/local/include/libksuid/ksuid.h
test -f staging/usr/local/include/libksuid/ksuid_version.h
test -f staging/usr/local/share/doc/libksuid/LICENSE
test -f staging/usr/local/share/doc/libksuid/LICENSE.MIT
test -f staging/usr/local/share/doc/libksuid/NOTICE
Expand All @@ -122,6 +123,45 @@ jobs:
find staging -name 'libksuid.a' -print | grep -q .
find staging -name 'libksuid.so' -print | grep -q .

# ==========================================================================
# Phase 2b: meson dist round-trip
# Builds a source tarball, extracts it into a clean tree, and rebuilds
# the library + tests from the tarball. Catches reproducibility
# regressions in configure_file templates (R2 from issue #3): if a
# future contributor adds vcs_tag, __DATE__, or anything build-time-
# variable to a generated header, this job fails before the PR can
# land. Runs in parallel to the build matrix.
# ==========================================================================
dist:
name: meson dist round-trip
needs: [lint]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # meson dist needs full git history

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y meson ninja-build

- name: Build source tarball
run: |
meson setup builddir-dist
meson dist -C builddir-dist --no-tests --formats xztar

- name: Rebuild from tarball + run tests
run: |
set -eux
tarball=$(ls builddir-dist/meson-dist/libksuid-*.tar.xz)
extract_root=$(mktemp -d)
tar -xJf "$tarball" -C "$extract_root"
src=$(ls -d "$extract_root"/libksuid-*)
meson setup "$src/build"
meson compile -C "$src/build"
meson test -C "$src/build" --print-errorlogs

# ==========================================================================
# Phase 3: Sanitizers (ASan + UBSan)
# Depends on Phase 2 build matrix passing.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ coverage/
.DS_Store
.omc/
*~

# Belt-and-braces: configure_file output normally lives under build/
# (already ignored), but if a stranger generates it in-tree we still
# want git to refuse to track the auto-generated copy.
libksuid/ksuid_version.h
2 changes: 2 additions & 0 deletions libksuid/ksuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#ifndef KSUID_H
#define KSUID_H

#include <libksuid/ksuid_version.h>

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
Expand Down
27 changes: 27 additions & 0 deletions libksuid/ksuid_version.h.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-3.0-or-later
*
* libksuid version macros. THIS FILE IS GENERATED by meson from
* libksuid/ksuid_version.h.in -- the source of truth lives in the
* top-level meson.build's `project(version : ...)` clause. Do not
* edit the generated copy under <build>/libksuid/ksuid_version.h.
*/
#ifndef KSUID_VERSION_H
#define KSUID_VERSION_H

#define KSUID_VERSION_MAJOR @VERSION_MAJOR@
#define KSUID_VERSION_MINOR @VERSION_MINOR@
#define KSUID_VERSION_PATCH @VERSION_PATCH@

/* Single integer suitable for `#if KSUID_VERSION >= ...` checks.
* Layout: bits 16-23 major, 8-15 minor, 0-7 patch. */
#define KSUID_VERSION \
((KSUID_VERSION_MAJOR << 16) \
| (KSUID_VERSION_MINOR << 8) \
| (KSUID_VERSION_PATCH ))

/* Full version string verbatim from meson.project_version(); may
* contain a pre-release suffix (e.g. "0.2.0-rc1") that the integer
* macros above do not encode. */
#define KSUID_VERSION_STRING "@VERSION_STRING@"

#endif /* KSUID_VERSION_H */
32 changes: 32 additions & 0 deletions libksuid/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generate libksuid/ksuid_version.h from meson.project_version() so
# downstream consumers can guard new API surface with `#if
# KSUID_VERSION >= ...`. Output lands at
# ${MESON_BUILD_ROOT}/libksuid/ksuid_version.h, which the parent
# include_directories('.') puts on the search path so the umbrella
# header can resolve <libksuid/ksuid_version.h> uniformly in-tree
# and post-install.

ver_parts = meson.project_version().split('.')
assert(ver_parts.length() >= 3,
'libksuid project version must be MAJOR.MINOR.PATCH (got: '
+ meson.project_version() + ')')

# Drop a possible "-prerelease" / "+build" suffix from the patch
# component before .to_int() validates that the three numeric macros
# really are integers. The full original string still rides through
# KSUID_VERSION_STRING so callers that care can read the suffix.
ver_patch_clean = ver_parts[2].split('-')[0].split('+')[0]

ver_conf = configuration_data()
ver_conf.set('VERSION_MAJOR', ver_parts[0].to_int())
ver_conf.set('VERSION_MINOR', ver_parts[1].to_int())
ver_conf.set('VERSION_PATCH', ver_patch_clean.to_int())
ver_conf.set('VERSION_STRING', meson.project_version())

ksuid_version_h = configure_file(
input : 'ksuid_version.h.in',
output : 'ksuid_version.h',
configuration : ver_conf,
install : true,
install_dir : get_option('includedir') / 'libksuid',
)
6 changes: 6 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ endif
inc = include_directories('.')
public_headers = files('libksuid/ksuid.h')

# Generate libksuid/ksuid_version.h alongside the source tree's
# libksuid/. The configure_file lives in libksuid/meson.build so
# its output lands at ${MESON_BUILD_ROOT}/libksuid/ksuid_version.h
# and resolves through |inc| as <libksuid/ksuid_version.h>.
subdir('libksuid')

core_sources = files(
'libksuid/ksuid.c',
'libksuid/base62.c',
Expand Down
29 changes: 29 additions & 0 deletions tests/test_smoke.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,34 @@ test_compare_first_byte_dominates (void)
ASSERT_TRUE (ksuid_compare (&b, &a) < 0);
}

static void
test_version_macros_are_consistent (void)
{
/* DELIBERATE SYNC POINT: these literal values must equal the
* `version :` field in the top-level meson.build. The test exists
* to prove that meson.project_version() flows through the
* configure_file substitution into ksuid_version.h.in -- a
* `>= 0` check would silently accept an empty @VERSION_MAJOR@
* substitution that the C preprocessor turns into 0. A real
* regression in the substitution chain therefore fails here.
*
* When you bump meson.build's project version you MUST update
* these four asserts in the same commit. */
ASSERT_EQ_INT (KSUID_VERSION_MAJOR, 0);
ASSERT_EQ_INT (KSUID_VERSION_MINOR, 1);
ASSERT_EQ_INT (KSUID_VERSION_PATCH, 0);
ASSERT_EQ_STR (KSUID_VERSION_STRING, "0.1.0");

/* The composite KSUID_VERSION must equal the documented
* (MAJOR << 16) | (MINOR << 8) | PATCH layout for `#if
* KSUID_VERSION >= ...` arithmetic to behave the way callers
* expect. */
int composed = (KSUID_VERSION_MAJOR << 16)
| (KSUID_VERSION_MINOR << 8)
| (KSUID_VERSION_PATCH);
ASSERT_EQ_INT (KSUID_VERSION, composed);
}

int
main (void)
{
Expand All @@ -56,5 +84,6 @@ main (void)
RUN_TEST (test_max_is_all_ff);
RUN_TEST (test_compare_orders_lex);
RUN_TEST (test_compare_first_byte_dominates);
RUN_TEST (test_version_macros_are_consistent);
TEST_MAIN_END ();
}
Loading