diff options
| author | Berke Güzel <berke.guzel@gdh.digital> | 2026-10-15 12:29:25 +0300 |
|---|---|---|
| committer | Berke Güzel <berke.guzel@gdh.digital> | 2026-10-15 12:29:25 +0300 |
| commit | 57ebf3b3238a165c1973ecdf96ba79f95d650fc9 (patch) | |
| tree | 6c6ee6ee17a32d392d068ded61f7a5483ef73e7a | |
| parent | 5f03033b0e211f9ed13e7a1351f9a8f9011ab665 (diff) | |
Add soversion, version macros, and reserved term_options words
The shared library now carries its major version in the SONAME
(libterm.so.0), so an ABI break gets a new file name. The header
gains TERM_VERSION, TERM_VERSION_MAJOR/MINOR/PATCH and
TERM_VERSION_CODE, and term_version() returns the code the library was
built with. A test checks the macros against meson.build's project
version and against the runtime value.
term_options ends with uint32_t reserved[8]. term_new() returns NULL if
a word is nonzero, so later versions can give those words a meaning
without misreading a caller's leftover data. All-zero options stay
valid.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
| -rw-r--r-- | include/term/term.h | 15 | ||||
| -rw-r--r-- | src/meson.build | 6 | ||||
| -rw-r--r-- | src/screen.c | 3 | ||||
| -rw-r--r-- | src/term.c | 5 | ||||
| -rw-r--r-- | tests/meson.build | 11 | ||||
| -rw-r--r-- | tests/term_test.c | 22 | ||||
| -rw-r--r-- | tests/version_test.c | 27 |
7 files changed, 89 insertions, 0 deletions
diff --git a/include/term/term.h b/include/term/term.h index dc41114..aa3178e 100644 --- a/include/term/term.h +++ b/include/term/term.h @@ -41,6 +41,16 @@ struct term_snapshot; #define TERM_SCROLLBACK_DEFAULT 10000 /* + * The version of this header. A host compares TERM_VERSION_CODE with + * term_version() to check that the library matches the header. + */ +#define TERM_VERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) +#define TERM_VERSION_MAJOR 0 +#define TERM_VERSION_MINOR 1 +#define TERM_VERSION_PATCH 0 +#define TERM_VERSION_CODE TERM_VERSION(TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH) + +/* * Colors. A term_color is either a direct color (alpha 0xFF, then RGB) or a * palette slot: an index 0-255, the default foreground, or the default * background. A slot resolves when a snapshot is painted, so a theme change @@ -147,12 +157,17 @@ struct term_options { enum term_cursor_style cursor_style; bool cursor_steady; /* false: the cursor blinks */ struct term_callbacks callbacks; + /* Must be zero. term_new() returns NULL if any word is nonzero, so a + * later version can give a word a meaning. */ + uint32_t reserved[8]; }; /* NULL when memory runs out. */ /* The Unicode version of the width tables, such as "18.0.0". A host can * pass it to programs as UNICODE_VERSION. */ TERM_API const char *term_unicode_version(void); +/* The TERM_VERSION_CODE the library was built with. */ +TERM_API unsigned term_version(void); /* The length of the prefix of buf made of bytes in [0x20, hi). With hi * 0x7f, it finds a run of printable ASCII in UTF-8 text, so a host can diff --git a/src/meson.build b/src/meson.build index 694ee0e..7223caa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,9 @@ subdir('osc') +# The SONAME carries the major version. An ABI break bumps it, so an old +# binary keeps the old library. +term_soversion = meson.project_version().split('.')[0] + # The x86-64 SIMD kernels pick their level at run time (see simd.h), so the # library needs no ISA flags. -march in c_args sets the lowest level. term_lib = both_libraries('term', @@ -23,6 +27,8 @@ term_lib = both_libraries('term', include_directories: term_private_inc, dependencies: [m_dep], gnu_symbol_visibility: 'hidden', + version: meson.project_version(), + soversion: term_soversion, install: true ) diff --git a/src/screen.c b/src/screen.c index 87d6f2c..c41234f 100644 --- a/src/screen.c +++ b/src/screen.c @@ -847,6 +847,9 @@ struct term *term_new(const struct term_options *opt) static const struct term_options defaults; if (!opt) opt = &defaults; + for (size_t i = 0; i < sizeof(opt->reserved) / sizeof(opt->reserved[0]); i++) + if (opt->reserved[i]) + return NULL; int cols = opt->size.x < 1 ? 80 : opt->size.x; int rows = opt->size.y < 1 ? 24 : opt->size.y; if (cols > TERM_MAX_COLS) @@ -8,6 +8,11 @@ const char *term_unicode_version(void) return UNICODE_DATA_VERSION; } +unsigned term_version(void) +{ + return TERM_VERSION_CODE; +} + void term_lock(struct term *t) { term_write_begin(t); diff --git a/tests/meson.build b/tests/meson.build index 69f3c49..5c77412 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -117,6 +117,17 @@ if cc.has_link_argument('-Wl,--wrap=realloc') and cc.has_link_argument('-Wl,--wr suite: 'unit', ) endif + +# The project version comes in as a define, so the header macros cannot +# drift from meson.build. +version_test = executable('version-test', 'version_test.c', + c_args: test_c_args + ['-DTERM_TEST_PROJECT_VERSION="@0@"'.format(meson.project_version())], + dependencies: [term_internal_dep], +) +test('version-test', version_test, + env: test_env, + suite: 'unit', +) test('term-cross-test', bash, args: [files('cross_test.sh'), meson.project_source_root()], suite: 'portability', diff --git a/tests/term_test.c b/tests/term_test.c index 66dbd96..2040e20 100644 --- a/tests/term_test.c +++ b/tests/term_test.c @@ -1832,8 +1832,30 @@ static void test_concurrent_feed_and_snapshot(void) term_free(t); } +static void test_reserved_options_must_be_zero(void) +{ + struct term_options opt = { .size = { .x = 10, .y = 2 } }; + struct term *t = term_new(&opt); + assert(t != NULL); + term_free(t); + + size_t words = sizeof(opt.reserved) / sizeof(opt.reserved[0]); + for (size_t i = 0; i < words; i++) { + opt.reserved[i] = 1; + assert(term_new(&opt) == NULL); + opt.reserved[i] = 0; + } + opt.reserved[words - 1] = 0x80000000u; + assert(term_new(&opt) == NULL); + opt.reserved[words - 1] = 0; + t = term_new(&opt); + assert(t != NULL); + term_free(t); +} + int main(void) { + test_reserved_options_must_be_zero(); test_concurrent_feed_and_snapshot(); test_grow_past_reservation(); test_skipped_cells_read_blank(); diff --git a/tests/version_test.c b/tests/version_test.c new file mode 100644 index 0000000..0eb889e --- /dev/null +++ b/tests/version_test.c @@ -0,0 +1,27 @@ +#include "term/term.h" + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +/* The project version from meson.build. The build defines it. */ +#ifndef TERM_TEST_PROJECT_VERSION +#error "define TERM_TEST_PROJECT_VERSION as the project version, such as \"0.1.0\"" +#endif + +int main(void) +{ + char text[32]; + snprintf(text, sizeof(text), "%d.%d.%d", + TERM_VERSION_MAJOR, TERM_VERSION_MINOR, TERM_VERSION_PATCH); + assert(!strcmp(text, TERM_TEST_PROJECT_VERSION)); + + int major, minor, patch; + assert(sscanf(TERM_TEST_PROJECT_VERSION, "%d.%d.%d", &major, &minor, &patch) == 3); + assert(TERM_VERSION_CODE == TERM_VERSION(major, minor, patch)); + assert(term_version() == TERM_VERSION_CODE); + + assert(TERM_VERSION(0, 2, 0) > TERM_VERSION(0, 1, 255)); + assert(TERM_VERSION(1, 0, 0) > TERM_VERSION(0, 255, 255)); + return 0; +} |
