Merge pull request #36381 from bfredl/nobilium

feat(build): HAVE_UNIBILIUM
This commit is contained in:
bfredl
2025-10-31 10:17:02 +01:00
committed by GitHub
10 changed files with 95 additions and 63 deletions

View File

@@ -288,6 +288,18 @@ cmake --build build
- Using `ninja` is strongly recommended. - 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/`). 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) ### 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) 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) option(ENABLE_LTO "enable link time optimization" ON)
endif() endif()
option(ENABLE_LIBINTL "enable libintl" ON) option(ENABLE_LIBINTL "enable libintl" ON)
option(ENABLE_UNIBILIUM "enable unibilium" ON)
option(ENABLE_WASMTIME "enable wasmtime" OFF) option(ENABLE_WASMTIME "enable wasmtime" OFF)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") 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 // without cross_compiling we like to reuse libluv etc at the same optimize level
const optimize_host = if (cross_compiling) .ReleaseSafe else optimize; 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" // puc lua 5.1 is not ReleaseSafe "safe"
const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize; 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; } else libluv;
const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize }); 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 // TODO(bfredl): fix upstream bugs with UBSAN
const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast }); const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast });
@@ -250,7 +252,7 @@ pub fn build(b: *std.Build) !void {
libuv.getEmittedIncludeTree(), libuv.getEmittedIncludeTree(),
libluv.getEmittedIncludeTree(), libluv.getEmittedIncludeTree(),
utf8proc.artifact("utf8proc").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(), treesitter.artifact("tree-sitter").getEmittedIncludeTree(),
if (iconv) |dep| dep.artifact("iconv").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), 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); nvim_exe.linkLibrary(libluv);
if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv")); if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv"));
nvim_exe.linkLibrary(utf8proc.artifact("utf8proc")); 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")); nvim_exe.linkLibrary(treesitter.artifact("tree-sitter"));
if (is_windows) { if (is_windows) {
nvim_exe.linkSystemLibrary("netapi32"); nvim_exe.linkSystemLibrary("netapi32");
@@ -317,6 +319,7 @@ pub fn build(b: *std.Build) !void {
if (is_windows) "-DMSWIN" else "", if (is_windows) "-DMSWIN" else "",
if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "", if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "",
if (is_windows) "-DUTF8PROC_STATIC" else "", if (is_windows) "-DUTF8PROC_STATIC" else "",
if (use_unibilium) "-DHAVE_UNIBILIUM" else "",
}; };
nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags }); nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags });

View File

@@ -30,7 +30,7 @@
.hash = "libuv-1.51.0-htqqv6liAADxBLIBCZT-qUh_3nRRwtNYsOFQOUmrd_sx", .hash = "libuv-1.51.0-htqqv6liAADxBLIBCZT-qUh_3nRRwtNYsOFQOUmrd_sx",
}, },
.utf8proc = .{ .path = "./deps/utf8proc/" }, .utf8proc = .{ .path = "./deps/utf8proc/" },
.unibilium = .{ .path = "./deps/unibilium/" }, .unibilium = .{ .path = "./deps/unibilium/", .lazy = true },
.libiconv = .{ .libiconv = .{
.url = "git+https://github.com/allyourcodebase/libiconv#9def4c8a1743380e85bcedb80f2c15b455e236f3", .url = "git+https://github.com/allyourcodebase/libiconv#9def4c8a1743380e85bcedb80f2c15b455e236f3",
.hash = "libiconv-1.18.0-p9sJwWnqAACzVYeWgXB5r5lOQ74XwTPlptixV0JPRO28", .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 • 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 currently limited in functionality, and CMake remains the recommended option
for the time being. 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 DEFAULTS
• 'diffopt' default value now includes "indent-heuristic" and "inline:char". • '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 - 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 `has('gui_running')` |has()| or see |nvim_list_uis()| for an example of
how to inspect the UI channel. how to inspect the UI channel.
- "builtin_x" means one of the |builtin-terms| was chosen, because the expected - Nvim might optionally be compiled with unibilium, in which case the terminfo
terminfo file was not found on the system. 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 - Nvim will use 256-colour capability on Linux virtual terminals. Vim uses
only 8 colours plus bright foreground on Linux VTs. only 8 colours plus bright foreground on Linux VTs.
- Vim combines what is in its |builtin-terms| with what it reads from terminfo, - 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(Libuv 1.28.0 REQUIRED)
find_package(Lpeg REQUIRED) find_package(Lpeg REQUIRED)
find_package(Treesitter 0.25.0 REQUIRED) find_package(Treesitter 0.25.0 REQUIRED)
find_package(Unibilium 2.0 REQUIRED)
find_package(UTF8proc REQUIRED) find_package(UTF8proc REQUIRED)
target_link_libraries(main_lib INTERFACE target_link_libraries(main_lib INTERFACE
iconv iconv
lpeg lpeg
treesitter treesitter
unibilium
utf8proc) utf8proc)
target_link_libraries(nlua0 PUBLIC lpeg) target_link_libraries(nlua0 PUBLIC lpeg)
@@ -48,6 +46,12 @@ if(ENABLE_LIBINTL)
target_link_libraries(main_lib INTERFACE libintl) target_link_libraries(main_lib INTERFACE libintl)
endif() 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) if(ENABLE_WASMTIME)
find_package(Wasmtime 29.0.1 EXACT REQUIRED) find_package(Wasmtime 29.0.1 EXACT REQUIRED)
target_link_libraries(main_lib INTERFACE wasmtime) target_link_libraries(main_lib INTERFACE wasmtime)

View File

@@ -2,7 +2,10 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#ifdef HAVE_UNIBILIUM
# include <unibilium.h> # include <unibilium.h>
#endif
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/api/private/defs.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) const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname)
{ {
if (terminfo_is_term_family(term, "xterm")) { if (terminfo_is_term_family(term, "xterm")) {
*termname = "builtin_xterm"; *termname = "xterm";
return &xterm_256colour_terminfo; return &xterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "screen")) { } else if (terminfo_is_term_family(term, "screen")) {
*termname = "builtin_screen"; *termname = "screen";
return &screen_256colour_terminfo; return &screen_256colour_terminfo;
} else if (terminfo_is_term_family(term, "tmux")) { } else if (terminfo_is_term_family(term, "tmux")) {
*termname = "builtin_tmux"; *termname = "tmux";
return &tmux_256colour_terminfo; return &tmux_256colour_terminfo;
} else if (terminfo_is_term_family(term, "rxvt")) { } else if (terminfo_is_term_family(term, "rxvt")) {
*termname = "builtin_rxvt"; *termname = "rxvt";
return &rxvt_256colour_terminfo; return &rxvt_256colour_terminfo;
} else if (terminfo_is_term_family(term, "putty")) { } else if (terminfo_is_term_family(term, "putty")) {
*termname = "builtin_putty"; *termname = "putty";
return &putty_256colour_terminfo; return &putty_256colour_terminfo;
} else if (terminfo_is_term_family(term, "linux")) { } else if (terminfo_is_term_family(term, "linux")) {
*termname = "builtin_linux"; *termname = "linux";
return &linux_16colour_terminfo; return &linux_16colour_terminfo;
} else if (terminfo_is_term_family(term, "interix")) { } else if (terminfo_is_term_family(term, "interix")) {
*termname = "builtin_interix"; *termname = "interix";
return &interix_8colour_terminfo; return &interix_8colour_terminfo;
} else if (terminfo_is_term_family(term, "iterm") } else if (terminfo_is_term_family(term, "iterm")
|| terminfo_is_term_family(term, "iterm2") || terminfo_is_term_family(term, "iterm2")
|| terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm.app")
|| terminfo_is_term_family(term, "iTerm2.app")) { || terminfo_is_term_family(term, "iTerm2.app")) {
*termname = "builtin_iterm"; *termname = "iterm";
return &iterm_256colour_terminfo; return &iterm_256colour_terminfo;
} else if (terminfo_is_term_family(term, "st")) { } else if (terminfo_is_term_family(term, "st")) {
*termname = "builtin_st"; *termname = "st";
return &st_256colour_terminfo; return &st_256colour_terminfo;
} else if (terminfo_is_term_family(term, "gnome") } else if (terminfo_is_term_family(term, "gnome")
|| terminfo_is_term_family(term, "vte")) { || terminfo_is_term_family(term, "vte")) {
*termname = "builtin_vte"; *termname = "vte";
return &vte_256colour_terminfo; return &vte_256colour_terminfo;
} else if (terminfo_is_term_family(term, "cygwin")) { } else if (terminfo_is_term_family(term, "cygwin")) {
*termname = "builtin_cygwin"; *termname = "cygwin";
return &cygwin_terminfo; return &cygwin_terminfo;
} else if (terminfo_is_term_family(term, "win32con")) { } else if (terminfo_is_term_family(term, "win32con")) {
*termname = "builtin_win32con"; *termname = "win32con";
return &win32con_terminfo; return &win32con_terminfo;
} else if (terminfo_is_term_family(term, "conemu")) { } else if (terminfo_is_term_family(term, "conemu")) {
*termname = "builtin_conemu"; *termname = "conemu";
return &conemu_terminfo; return &conemu_terminfo;
} else if (terminfo_is_term_family(term, "vtpcon")) { } else if (terminfo_is_term_family(term, "vtpcon")) {
*termname = "builtin_vtpcon"; *termname = "vtpcon";
return &vtpcon_terminfo; return &vtpcon_terminfo;
} else { } else {
*termname = "builtin_ansi"; *termname = "ansi";
return &ansi_terminfo; return &ansi_terminfo;
} }
} }
static ssize_t unibi_find_ext_str(unibi_term *ut, const char *name) bool terminfo_from_database(TerminfoEntry *ti, char *termname, Arena *arena)
{
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)
{ {
#ifdef HAVE_UNIBILIUM
unibi_term *ut = unibi_from_term(termname); unibi_term *ut = unibi_from_term(termname);
if (!ut) { if (!ut) {
return false; return false;
@@ -172,11 +164,16 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
# undef X # undef X
}; };
size_t max = unibi_count_ext_str(ut);
for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) { for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) {
ssize_t val = unibi_find_ext_str(ut, uni_ext[i]); const char *name = uni_ext[i];
if (val >= 0) { for (size_t val = 0; val < max; val++) {
const char *data = unibi_get_ext_str(ut, (size_t)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; ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL;
break;
}
} }
} }
@@ -212,6 +209,9 @@ bool terminfo_from_unibilium(TerminfoEntry *ti, char *termname, Arena *arena)
unibi_destroy(ut); unibi_destroy(ut);
return true; return true;
#else
return false;
#endif
} }
static const char *fmt(bool val) 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). /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim).
/// ///
/// @return allocated string /// @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; StringBuilder data = KV_INITIAL_VALUE;
kv_printf(data, "&term: %s\n", termname); 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, "\n");
kv_printf(data, "Boolean capabilities:\n"); kv_printf(data, "Boolean capabilities:\n");

View File

@@ -86,6 +86,7 @@ struct TUIData {
int row, col; int row, col;
int out_fd; int out_fd;
int pending_resize_events; int pending_resize_events;
bool terminfo_found_in_db;
bool can_change_scroll_region; bool can_change_scroll_region;
bool has_left_and_right_margin_mode; bool has_left_and_right_margin_mode;
bool has_sync_mode; bool has_sync_mode;
@@ -382,15 +383,15 @@ static void terminfo_start(TUIData *tui)
#endif #endif
// Set up terminfo. // Set up terminfo.
bool found_in_db = false; tui->terminfo_found_in_db = false;
if (term) { 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); 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); const TerminfoEntry *new = terminfo_from_builtin(term, &tui->term);
// we will patch it below, so make a copy // we will patch it below, so make a copy
memcpy(&tui->ti, new, sizeof tui->ti); 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(title, CSTR_AS_OBJ("Title"));
ADD_C(chunks, ARRAY_OBJ(title)); ADD_C(chunks, ARRAY_OBJ(title));
MAXSIZE_TEMP_ARRAY(info, 1); 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(info, STRING_OBJ(str));
ADD_C(chunks, ARRAY_OBJ(info)); ADD_C(chunks, ARRAY_OBJ(info));
MAXSIZE_TEMP_ARRAY(end_fold, 2); 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() it('in nvim_list_uis(), sets nvim_set_client_info()', function()
-- $TERM in :terminal. -- $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 ui_chan = 1
local expected = { local expected = {
{ {
@@ -3447,14 +3447,14 @@ describe("TUI 'term' option", function()
end end
it('gets builtin term if $TERM is invalid', function() it('gets builtin term if $TERM is invalid', function()
assert_term('foo', 'builtin_ansi') assert_term('foo', 'ansi')
end) end)
it('gets system-provided term if $TERM is valid', function() it('gets system-provided term if $TERM is valid', function()
if is_os('openbsd') then if is_os('openbsd') then
assert_term('xterm', 'xterm') assert_term('xterm', 'xterm')
elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. 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 elseif is_os('mac') then
local status, _ = pcall(assert_term, 'xterm', 'xterm') local status, _ = pcall(assert_term, 'xterm', 'xterm')
if not status then if not status then
@@ -3467,9 +3467,9 @@ describe("TUI 'term' option", function()
it('builtin terms', function() it('builtin terms', function()
-- These non-standard terminfos are always builtin. -- These non-standard terminfos are always builtin.
assert_term('win32con', 'builtin_win32con') assert_term('win32con', 'win32con')
assert_term('conemu', 'builtin_conemu') assert_term('conemu', 'conemu')
assert_term('vtpcon', 'builtin_vtpcon') assert_term('vtpcon', 'vtpcon')
end) end)
end) end)