feat(build): HAVE_UNIBILIUM

compile time features are hot again.

Note: this changes the &term value for builtin definition from
'builtin_xterm' to just 'xterm'. It's an xterm regardless of we use an
external definition or an internal. Prior to this commit the vast
majority of POSIX users will have used external terminfo, so plugins and
scripts are only going to have checked for &term == 'xterm' or 'tmux' or
whatever.
The status of external loading is still available in "nvim -V3" output.
This commit is contained in:
bfredl
2025-10-28 10:05:34 +01:00
parent ce9a73625a
commit e1ffa8d33b
10 changed files with 95 additions and 63 deletions

View File

@@ -288,6 +288,18 @@ cmake --build build
- Using `ninja` is strongly recommended.
4. If treesitter parsers are not bundled, they need to be available in a `parser/` runtime directory (e.g. `/usr/share/nvim/runtime/parser/`).
### How to build without unibilium
Unibilium is the only dependency which is licensed under LGPLv3 (there are no
GPLv3-only dependencies). This library is used for loading the terminfo database at
runtime, and can be disabled if the internal definitions for common terminals
are good enough. To avoid this dependency, build with support for loading
custom terminfo at runtime, use
```sh
make CMAKE_EXTRA_FLAGS="-DENABLE_UNIBILIUM=0" BUNDLED_CMAKE_FLAG="-DUSE_BUNDLED_UNIBILIUM=0"
```
### How to build static binary (on Linux)
1. Use a linux distribution which uses musl C. We will use Alpine Linux but any distro with musl should work. (glibc does not support static linking)

View File

@@ -130,6 +130,7 @@ else()
option(ENABLE_LTO "enable link time optimization" ON)
endif()
option(ENABLE_LIBINTL "enable libintl" ON)
option(ENABLE_UNIBILIUM "enable unibilium" ON)
option(ENABLE_WASMTIME "enable wasmtime" OFF)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")

View File

@@ -46,6 +46,8 @@ pub fn build(b: *std.Build) !void {
// without cross_compiling we like to reuse libluv etc at the same optimize level
const optimize_host = if (cross_compiling) .ReleaseSafe else optimize;
const use_unibilium = b.option(bool, "unibilium", "use unibilium") orelse true;
// puc lua 5.1 is not ReleaseSafe "safe"
const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize;
@@ -92,7 +94,7 @@ pub fn build(b: *std.Build) !void {
} else libluv;
const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize });
const unibilium = b.dependency("unibilium", .{ .target = target, .optimize = optimize });
const unibilium = if (use_unibilium) b.lazyDependency("unibilium", .{ .target = target, .optimize = optimize }) else null;
// TODO(bfredl): fix upstream bugs with UBSAN
const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast });
@@ -250,7 +252,7 @@ pub fn build(b: *std.Build) !void {
libuv.getEmittedIncludeTree(),
libluv.getEmittedIncludeTree(),
utf8proc.artifact("utf8proc").getEmittedIncludeTree(),
unibilium.artifact("unibilium").getEmittedIncludeTree(),
if (unibilium) |u| u.artifact("unibilium").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), // :p
treesitter.artifact("tree-sitter").getEmittedIncludeTree(),
if (iconv) |dep| dep.artifact("iconv").getEmittedIncludeTree() else b.path("UNUSED_PATH/"),
};
@@ -279,7 +281,7 @@ pub fn build(b: *std.Build) !void {
nvim_exe.linkLibrary(libluv);
if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv"));
nvim_exe.linkLibrary(utf8proc.artifact("utf8proc"));
nvim_exe.linkLibrary(unibilium.artifact("unibilium"));
if (unibilium) |u| nvim_exe.linkLibrary(u.artifact("unibilium"));
nvim_exe.linkLibrary(treesitter.artifact("tree-sitter"));
if (is_windows) {
nvim_exe.linkSystemLibrary("netapi32");
@@ -317,6 +319,7 @@ pub fn build(b: *std.Build) !void {
if (is_windows) "-DMSWIN" else "",
if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "",
if (is_windows) "-DUTF8PROC_STATIC" else "",
if (use_unibilium) "-DHAVE_UNIBILIUM" else "",
};
nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags });

View File

@@ -30,7 +30,7 @@
.hash = "libuv-1.51.0-htqqv6liAADxBLIBCZT-qUh_3nRRwtNYsOFQOUmrd_sx",
},
.utf8proc = .{ .path = "./deps/utf8proc/" },
.unibilium = .{ .path = "./deps/unibilium/" },
.unibilium = .{ .path = "./deps/unibilium/", .lazy = true },
.libiconv = .{
.url = "git+https://github.com/allyourcodebase/libiconv#9def4c8a1743380e85bcedb80f2c15b455e236f3",
.hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28",

View File

@@ -153,7 +153,12 @@ BUILD
• A Zig-based build system has been added as an alternative to CMake. It is
currently limited in functionality, and CMake remains the recommended option
for the time being.
• Nvim can be built without Unibilium (terminfo implementation), in which case
the user's terminfo database won't be loaded and only internal definitions
for the most common terminals are used. >
make distclean && make CMAKE_EXTRA_FLAGS="-DENABLE_UNIBILIUM=0" BUNDLED_CMAKE_FLAG="-DUSE_BUNDLED_UNIBILIUM=0"
<
DEFAULTS
• 'diffopt' default value now includes "indent-heuristic" and "inline:char".

View File

@@ -473,8 +473,9 @@ TUI:
- Note: If you want to detect when Nvim is running in a terminal, use
`has('gui_running')` |has()| or see |nvim_list_uis()| for an example of
how to inspect the UI channel.
- "builtin_x" means one of the |builtin-terms| was chosen, because the expected
terminfo file was not found on the system.
- Nvim might optionally be compiled with unibilium, in which case the terminfo
database will be used. Otherwise, or if the terminal was not found in
the database, a table of builtin terminal definitions will be used.
- Nvim will use 256-colour capability on Linux virtual terminals. Vim uses
only 8 colours plus bright foreground on Linux VTs.
- Vim combines what is in its |builtin-terms| with what it reads from terminfo,

View File

@@ -32,14 +32,12 @@ find_package(Iconv REQUIRED)
find_package(Libuv 1.28.0 REQUIRED)
find_package(Lpeg REQUIRED)
find_package(Treesitter 0.25.0 REQUIRED)
find_package(Unibilium 2.0 REQUIRED)
find_package(UTF8proc REQUIRED)
target_link_libraries(main_lib INTERFACE
iconv
lpeg
treesitter
unibilium
utf8proc)
target_link_libraries(nlua0 PUBLIC lpeg)
@@ -48,6 +46,12 @@ if(ENABLE_LIBINTL)
target_link_libraries(main_lib INTERFACE libintl)
endif()
if(ENABLE_UNIBILIUM)
find_package(Unibilium 2.0 REQUIRED)
target_compile_definitions(nvim_bin PRIVATE HAVE_UNIBILIUM)
target_link_libraries(main_lib INTERFACE unibilium)
endif()
if(ENABLE_WASMTIME)
find_package(Wasmtime 29.0.1 EXACT REQUIRED)
target_link_libraries(main_lib INTERFACE wasmtime)

View File

@@ -2,7 +2,10 @@
#include <stdbool.h>
#include <string.h>
#include <unibilium.h>
#ifdef HAVE_UNIBILIUM
# include <unibilium.h>
#endif
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
@@ -68,71 +71,60 @@ bool terminfo_is_bsd_console(const char *term)
const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname)
{
if (terminfo_is_term_family(term, "xterm")) {
*termname = "builtin_xterm";
*termname = "xterm";
return &xterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "screen")) {
*termname = "builtin_screen";
*termname = "screen";
return &screen_256colour_terminfo;
} else if (terminfo_is_term_family(term, "tmux")) {
*termname = "builtin_tmux";
*termname = "tmux";
return &tmux_256colour_terminfo;
} else if (terminfo_is_term_family(term, "rxvt")) {
*termname = "builtin_rxvt";
*termname = "rxvt";
return &rxvt_256colour_terminfo;
} else if (terminfo_is_term_family(term, "putty")) {
*termname = "builtin_putty";
*termname = "putty";
return &putty_256colour_terminfo;
} else if (terminfo_is_term_family(term, "linux")) {
*termname = "builtin_linux";
*termname = "linux";
return &linux_16colour_terminfo;
} else if (terminfo_is_term_family(term, "interix")) {
*termname = "builtin_interix";
*termname = "interix";
return &interix_8colour_terminfo;
} else if (terminfo_is_term_family(term, "iterm")
|| terminfo_is_term_family(term, "iterm2")
|| terminfo_is_term_family(term, "iTerm.app")
|| terminfo_is_term_family(term, "iTerm2.app")) {
*termname = "builtin_iterm";
*termname = "iterm";
return &iterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "st")) {
*termname = "builtin_st";
*termname = "st";
return &st_256colour_terminfo;
} else if (terminfo_is_term_family(term, "gnome")
|| terminfo_is_term_family(term, "vte")) {
*termname = "builtin_vte";
*termname = "vte";
return &vte_256colour_terminfo;
} else if (terminfo_is_term_family(term, "cygwin")) {
*termname = "builtin_cygwin";
*termname = "cygwin";
return &cygwin_terminfo;
} else if (terminfo_is_term_family(term, "win32con")) {
*termname = "builtin_win32con";
*termname = "win32con";
return &win32con_terminfo;
} else if (terminfo_is_term_family(term, "conemu")) {
*termname = "builtin_conemu";
*termname = "conemu";
return &conemu_terminfo;
} else if (terminfo_is_term_family(term, "vtpcon")) {
*termname = "builtin_vtpcon";
*termname = "vtpcon";
return &vtpcon_terminfo;
} else {
*termname = "builtin_ansi";
*termname = "ansi";
return &ansi_terminfo;
}
}
static ssize_t unibi_find_ext_str(unibi_term *ut, const char *name)
{
size_t max = unibi_count_ext_str(ut);
for (size_t i = 0; i < max; i++) {
const char *n = unibi_get_ext_str_name(ut, i);
if (n && 0 == strcmp(n, name)) {
return (ssize_t)i;
}
}
return -1;
}
bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
bool terminfo_from_database(TerminfoEntry *ti, char *termname, Arena *arena)
{
#ifdef HAVE_UNIBILIUM
unibi_term *ut = unibi_from_term(termname);
if (!ut) {
return false;
@@ -156,9 +148,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
}
static const enum unibi_string uni_ids[] = {
#define X(name) unibi_##name,
# define X(name) unibi_##name,
XLIST_TERMINFO_BUILTIN
#undef X
# undef X
};
for (size_t i = 0; i < ARRAY_SIZE(uni_ids); i++) {
@@ -167,26 +159,31 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
}
static const char *uni_ext[] = {
#define X(informal_name, terminfo_name) #terminfo_name,
# define X(informal_name, terminfo_name) #terminfo_name,
XLIST_TERMINFO_EXT
#undef X
# undef X
};
size_t max = unibi_count_ext_str(ut);
for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) {
ssize_t val = unibi_find_ext_str(ut, uni_ext[i]);
if (val >= 0) {
const char *data = unibi_get_ext_str(ut, (size_t)val);
ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL;
const char *name = uni_ext[i];
for (size_t val = 0; val < max; val++) {
const char *n = unibi_get_ext_str_name(ut, val);
if (n && strequal(n, name)) {
const char *data = unibi_get_ext_str(ut, val);
ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL;
break;
}
}
}
#define X(name) { unibi_key_##name, unibi_string_begin_ },
#define Y(name) { unibi_key_##name, unibi_key_s##name },
# define X(name) { unibi_key_##name, unibi_string_begin_ },
# define Y(name) { unibi_key_##name, unibi_key_s##name },
static const enum unibi_string uni_keys[][2] = {
XYLIST_TERMINFO_KEYS
};
#undef X
#undef Y
# undef X
# undef Y
for (size_t i = 0; i < ARRAY_SIZE(uni_keys); i++) {
const char *val = unibi_get_str(ut, uni_keys[i][0]);
@@ -200,9 +197,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
}
static const enum unibi_string uni_fkeys[] = {
#define X(name) unibi_key_##name,
# define X(name) unibi_key_##name,
XLIST_TERMINFO_FKEYS
#undef X
# undef X
};
for (size_t i = 0; i < ARRAY_SIZE(uni_fkeys); i++) {
@@ -212,6 +209,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
unibi_destroy(ut);
return true;
#else
return false;
#endif
}
static const char *fmt(bool val)
@@ -223,11 +223,16 @@ static const char *fmt(bool val)
/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim).
///
/// @return allocated string
String terminfo_info_msg(const TerminfoEntry *ti, const char *termname)
String terminfo_info_msg(const TerminfoEntry *ti, const char *termname, bool from_db)
{
StringBuilder data = KV_INITIAL_VALUE;
kv_printf(data, "&term: %s\n", termname);
if (from_db) {
kv_printf(data, "using terminfo database\n");
} else {
kv_printf(data, "using builtin terminfo\n");
}
kv_printf(data, "\n");
kv_printf(data, "Boolean capabilities:\n");

View File

@@ -86,6 +86,7 @@ struct TUIData {
int row, col;
int out_fd;
int pending_resize_events;
bool terminfo_found_in_db;
bool can_change_scroll_region;
bool has_left_and_right_margin_mode;
bool has_sync_mode;
@@ -382,15 +383,15 @@ static void terminfo_start(TUIData *tui)
#endif
// Set up terminfo.
bool found_in_db = false;
tui->terminfo_found_in_db = false;
if (term) {
if (terminfo_from_unibilium(&tui->ti, term, &tui->ti_arena)) {
if (terminfo_from_database(&tui->ti, term, &tui->ti_arena)) {
tui->term = arena_strdup(&tui->ti_arena, term);
found_in_db = true;
tui->terminfo_found_in_db = true;
}
}
if (!found_in_db) {
if (!tui->terminfo_found_in_db) {
const TerminfoEntry *new = terminfo_from_builtin(term, &tui->term);
// we will patch it below, so make a copy
memcpy(&tui->ti, new, sizeof tui->ti);
@@ -1596,7 +1597,7 @@ static void show_verbose_terminfo(TUIData *tui)
ADD_C(title, CSTR_AS_OBJ("Title"));
ADD_C(chunks, ARRAY_OBJ(title));
MAXSIZE_TEMP_ARRAY(info, 1);
String str = terminfo_info_msg(&tui->ti, tui->term);
String str = terminfo_info_msg(&tui->ti, tui->term, tui->terminfo_found_in_db);
ADD_C(info, STRING_OBJ(str));
ADD_C(chunks, ARRAY_OBJ(info));
MAXSIZE_TEMP_ARRAY(end_fold, 2);

View File

@@ -2177,7 +2177,7 @@ describe('TUI', function()
it('in nvim_list_uis(), sets nvim_set_client_info()', function()
-- $TERM in :terminal.
local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color'
local exp_term = is_os('bsd') and 'xterm' or 'xterm-256color'
local ui_chan = 1
local expected = {
{
@@ -3447,14 +3447,14 @@ describe("TUI 'term' option", function()
end
it('gets builtin term if $TERM is invalid', function()
assert_term('foo', 'builtin_ansi')
assert_term('foo', 'ansi')
end)
it('gets system-provided term if $TERM is valid', function()
if is_os('openbsd') then
assert_term('xterm', 'xterm')
elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used.
assert_term('xterm', 'builtin_xterm')
assert_term('xterm', 'xterm')
elseif is_os('mac') then
local status, _ = pcall(assert_term, 'xterm', 'xterm')
if not status then
@@ -3467,9 +3467,9 @@ describe("TUI 'term' option", function()
it('builtin terms', function()
-- These non-standard terminfos are always builtin.
assert_term('win32con', 'builtin_win32con')
assert_term('conemu', 'builtin_conemu')
assert_term('vtpcon', 'builtin_vtpcon')
assert_term('win32con', 'win32con')
assert_term('conemu', 'conemu')
assert_term('vtpcon', 'vtpcon')
end)
end)