在 FreeBSD 上生成 Skia C 绑定库的方法 (利用 rust-skia)
引言:C++ ABI 不兼容性问题
前一篇帖子(如何在 FreeBSD 上编译 Skia)介绍了使用 gn 和 ninja 直接编译 Skia C++ 库 (libskia.a) 的方法。该方法适用于运行像 viewer 这样在 gn 构建系统内集成的示例。
然而,当尝试将 libskia.a 库链接到使用 Makefile 或 CMake 的外部 C/C++ 项目时,就会出现 C++ ABI (Application Binary Interface) 不兼容性问题。
- 原因: Skia 的 C++ 构建系统 (
gn) 会定义诸如skia_use_vulkan=true等预处理器标志 (-D...)。像DisplayParams.h这样的 Skia 头文件会根据这些标志动态改变结构体的内存布局(大小)。 - 问题: 使用
Makefile编译的 C 项目不知道gn使用的这些标志,因此导致库 (libskia.a) 和 C 项目代码中相同结构体的大小不一致,引发Invalid read或SIGSEGV(内存访问冲突)。
利用 rust-skia 的 C API 解决方案
rust-skia 项目提供了一个 C API 桥接,用于解决这个 ABI 问题。rust-skia 的 cargo build 过程执行以下操作:
- 使用
gn编译 Skia C++ 核心库 (libskia.a)。 - 捕获
gn使用的 C++ 编译标志。 - 使用第 2 步中捕获的相同标志编译 C API 封装 (
bindings.cpp),生成libskia-bindings.a。
通过此过程,libskia.a 和 libskia-bindings.a 成为一套 ABI 兼容的 C 库集合。
本文将说明如何利用 rust-skia 的构建系统生成 Skia C 库 (libskia.a, libskia-bindings.a) 以用于 C 工具包开发。
1. 下载 rust-skia 源代码
必须使用 git 克隆包含 skia 子模块的源代码。
# 基于 m142 (0.90.0) 版本克隆。
git clone --recursive --branch 0.90.0 https://github.com/rust-skia/rust-skia.git rust-skia-0.90.0
cd rust-skia-0.90.0
2. 对 Skia C++ 源代码进行打补丁
必须对 rust-skia 构建脚本将要编译的 skia-bindings/skia 子模块应用 FreeBSD 支持补丁。
-
移动到 Skia 子模块目录:
cd skia-bindings/skia -
应用补丁: (假设 FreeBSD 支持补丁文件位于
~/support-freebsd-skia-m142.diff。)commit bd66f5163f7ed1360c3d17c80fbe4c465d4e1171 Author: Hodong Kim <hodong@nimfsoft.art> Date: Fri Nov 14 01:31:35 2025 +0900 Support for FreeBSD, skia-m142 diff --git a/BUILD.gn b/BUILD.gn index 74de313c2f..0820913e62 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -31,7 +31,7 @@ config("skia_public") { if (is_component_build) { defines += [ "SKIA_DLL" ] } - if (is_linux) { + if (is_linux || is_freebsd) { defines += [ "SK_R32_SHIFT=16" ] } if (skia_enable_optimize_size) { @@ -589,6 +589,8 @@ if (skia_compile_modules) { sources += [ "src/utils/SkGetExecutablePath_mac.cpp" ] } else if (is_linux || is_android) { sources += [ "src/utils/SkGetExecutablePath_linux.cpp" ] + } else if (is_freebsd) { + sources += [ "src/utils/SkGetExecutablePath_freebsd.cpp" ] } if (is_win) { sources += skia_ports_windows_sources @@ -716,6 +718,8 @@ if (skia_compile_sksl_tests) { sources += [ "src/utils/SkGetExecutablePath_mac.cpp" ] } else if (is_linux || is_android) { sources += [ "src/utils/SkGetExecutablePath_linux.cpp" ] + } else if (is_freebsd) { + sources += [ "src/utils/SkGetExecutablePath_freebsd.cpp" ] } if (is_win) { sources += skia_ports_windows_sources @@ -1002,7 +1006,7 @@ optional("gpu") { } } else if (skia_use_webgl) { sources += [ "src/gpu/ganesh/gl/webgl/GrGLMakeNativeInterface_webgl.cpp" ] - } else if (is_linux && skia_use_x11) { + } else if ((is_linux || is_freebsd) && skia_use_x11) { sources += [ "src/gpu/ganesh/gl/glx/GrGLMakeGLXInterface.cpp", "src/gpu/ganesh/gl/glx/GrGLMakeNativeInterface_glx.cpp", @@ -1789,7 +1793,7 @@ skia_component("skia") { ] } - if (is_linux || is_wasm) { + if (is_linux || is_freebsd || is_wasm) { sources += [ "src/ports/SkDebug_stdio.cpp" ] if (skia_use_egl) { libs += [ "GLESv2" ] @@ -1985,7 +1989,7 @@ if (((skia_enable_fontmgr_fontconfig && skia_use_freetype) || # Targets guarded by skia_enable_tools may use //third_party freely. if (skia_enable_tools) { - if (is_linux && target_cpu == "x64") { + if ((is_linux || is_freebsd) && target_cpu == "x64") { skia_executable("fiddle") { check_includes = false libs = [] @@ -2155,7 +2159,7 @@ if (skia_enable_tools) { if (is_android || skia_use_egl) { sources += [ "tools/ganesh/gl/egl/CreatePlatformGLTestContext_egl.cpp" ] libs += [ "EGL" ] - } else if (is_linux) { + } else if (is_linux || is_freebsd) { sources += [ "tools/ganesh/gl/glx/CreatePlatformGLTestContext_glx.cpp" ] libs += [ "GLU", @@ -2586,7 +2590,7 @@ if (skia_enable_tools) { ] } - if (is_linux || is_mac || skia_enable_optimize_size) { + if (is_linux || is_freebsd || is_mac || skia_enable_optimize_size) { if (skia_enable_skottie) { test_app("skottie_tool") { deps = [ "modules/skottie:tool" ] @@ -2764,7 +2768,7 @@ if (skia_enable_tools) { } } - if (is_linux && skia_use_icu) { + if ((is_linux || is_freebsd) && skia_use_icu) { test_app("sktexttopdf") { sources = [ "tools/using_skia_and_harfbuzz.cpp" ] deps = [ @@ -2774,7 +2778,7 @@ if (skia_enable_tools) { } } - if (is_linux || is_mac) { + if (is_linux || is_freebsd || is_mac) { test_app("create_test_font") { sources = [ "tools/fonts/create_test_font.cpp" ] deps = [ ":skia" ] @@ -3023,7 +3027,7 @@ if (skia_enable_tools) { "tools/sk_app/android/surface_glue_android.h", ] libs += [ "android" ] - } else if (is_linux) { + } else if (is_linux || is_freebsd) { sources += [ "tools/sk_app/unix/Window_unix.cpp", "tools/sk_app/unix/Window_unix.h", @@ -3246,7 +3250,7 @@ if (skia_enable_tools) { } } - if (is_linux || is_win || is_mac) { + if (is_linux || is_freebsd || is_win || is_mac) { test_app("editor") { is_shared_library = is_android deps = [ "modules/skplaintexteditor:editor_app" ] diff --git a/bench/SkSLBench.cpp b/bench/SkSLBench.cpp index 2af4081af3..1e7c20d262 100644 --- a/bench/SkSLBench.cpp +++ b/bench/SkSLBench.cpp @@ -625,10 +625,25 @@ void main() #if defined(SK_BUILD_FOR_UNIX) +#ifdef __FreeBSD__ +#include <stdlib.h> +#include <malloc_np.h> + +static int64_t heap_bytes_used() { + size_t allocated; + size_t len = sizeof (allocated); + + if (!mallctl ("stats.allocated", &allocated, &len, NULL, 0)) + return allocated; + + return -1; +} +#else #include <malloc.h> static int64_t heap_bytes_used() { return (int64_t)mallinfo().uordblks; } +#endif #elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) diff --git a/gn/BUILDCONFIG.gn b/gn/BUILDCONFIG.gn index b08ab14c59..70ca1d5dfd 100644 --- a/gn/BUILDCONFIG.gn +++ b/gn/BUILDCONFIG.gn @@ -67,6 +67,7 @@ is_android = current_os == "android" is_ios = current_os == "ios" || current_os == "tvos" is_tvos = current_os == "tvos" is_linux = current_os == "linux" +is_freebsd = current_os == "freebsd" is_mac = current_os == "mac" is_wasm = current_os == "wasm" is_win = current_os == "win" diff --git a/gn/skia/BUILD.gn b/gn/skia/BUILD.gn index 75d8f62b7b..6b734690fe 100644 --- a/gn/skia/BUILD.gn +++ b/gn/skia/BUILD.gn @@ -253,7 +253,7 @@ config("default") { } } - if (is_linux) { + if (is_linux || is_freebsd) { libs += [ "pthread" ] } @@ -377,7 +377,7 @@ config("default") { ldflags += [ "-fsanitize=$sanitizers" ] } - if (is_linux) { + if (is_linux || is_freebsd) { cflags_cc += [ "-stdlib=libc++" ] ldflags += [ "-stdlib=libc++" ] } @@ -770,7 +770,7 @@ config("executable") { ] } else if (is_mac) { ldflags = [ "-Wl,-rpath,@loader_path/." ] - } else if (is_linux) { + } else if (is_linux || is_freebsd) { ldflags = [ "-rdynamic", "-Wl,-rpath,\$ORIGIN", diff --git a/gn/toolchain/BUILD.gn b/gn/toolchain/BUILD.gn index d4148ed6fa..41daf20c8e 100644 --- a/gn/toolchain/BUILD.gn +++ b/gn/toolchain/BUILD.gn @@ -306,7 +306,7 @@ template("gcc_like_toolchain") { rspfile = ".rsp" rspfile_content = "" rm_py = rebase_path("../rm.py") - command = "$shell python3 \"$rm_py\" \"\" && $ar rcs @$rspfile" + command = "$shell python3 \"$rm_py\" \"\" && $ar rcs `cat $rspfile`" } outputs = diff --git a/src/utils/BUILD.bazel b/src/utils/BUILD.bazel index 613abca937..c612f34475 100644 --- a/src/utils/BUILD.bazel +++ b/src/utils/BUILD.bazel @@ -153,6 +153,7 @@ skia_cc_library( "@platforms//os:windows": ["SkGetExecutablePath_win.cpp"], "@platforms//os:macos": ["SkGetExecutablePath_mac.cpp"], "@platforms//os:linux": ["SkGetExecutablePath_linux.cpp"], + "@platforms//os:freebsd": ["SkGetExecutablePath_freebsd.cpp"], }), hdrs = ["SkGetExecutablePath.h"], visibility = [ diff --git a/src/utils/SkGetExecutablePath_freebsd.cpp b/src/utils/SkGetExecutablePath_freebsd.cpp new file mode 100644 index 0000000000..d199b970db --- /dev/null +++ b/src/utils/SkGetExecutablePath_freebsd.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2022 Google Inc. + * Copyright (C) 2023-2025 Hodong Kim <hodong@nimfsoft.art> + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "tools/SkGetExecutablePath.h" +#include <sys/types.h> +#include <sys/sysctl.h> + +std::string SkGetExecutablePath() { + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + std::string result(PATH_MAX, '\0'); + + size_t len = result.size(); + + int retval = sysctl(mib, 4, &result[0], &len, NULL, 0); + + if (retval < 0) { + result.clear(); + } else { + result.resize((len > 0) ? (len - 1) : 0); + } + return result; +} diff --git a/tools/git-sync-deps b/tools/git-sync-deps index feaff62f29..ee1aa828a4 100755 --- a/tools/git-sync-deps +++ b/tools/git-sync-deps @@ -96,7 +96,7 @@ def is_git_toplevel(git, directory): # return which breaks the comparison. toplevel = subprocess.check_output( [git, 'rev-parse', '--path-format=relative', '--show-toplevel'], cwd=directory).strip() - return (os.path.normcase(os.path.realpath(directory)) == + return (os.path.normcase(os.path.realpath(directory)) == os.path.normcase(os.path.realpath(os.path.join(directory, toplevel.decode())))) except subprocess.CalledProcessError: return False @@ -259,7 +259,7 @@ def multithread(function, list_of_arg_lists): def main(argv): deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH) verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False)) - skip_emsdk = bool(os.environ.get('GIT_SYNC_DEPS_SKIP_EMSDK', False)) + #skip_emsdk = bool(os.environ.get('GIT_SYNC_DEPS_SKIP_EMSDK', False)) shallow = not ('--deep' in argv) if '--help' in argv or '-h' in argv: @@ -267,13 +267,13 @@ def main(argv): return 1 git_sync_deps(deps_file_path, argv, shallow, verbose) - subprocess.check_call( - [sys.executable, - os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')]) - if not skip_emsdk: - subprocess.check_call( - [sys.executable, - os.path.join(os.path.dirname(deps_file_path), 'bin', 'activate-emsdk')]) + #subprocess.check_call( + # [sys.executable, + # os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')]) + #if not skip_emsdk: + # subprocess.check_call( + # [sys.executable, + # os.path.join(os.path.dirname(deps_file_path), 'bin', 'activate-emsdk')]) return 0 diff --git a/tools/window/BUILD.gn b/tools/window/BUILD.gn index 4a30186dbd..7dfa84af71 100644 --- a/tools/window/BUILD.gn +++ b/tools/window/BUILD.gn @@ -38,7 +38,7 @@ skia_component("window") { "android/WindowContextFactory_android.h", ] libs += [ "android" ] - } else if (is_linux) { + } else if (is_linux || is_freebsd) { sources += [ "unix/RasterWindowContext_unix.cpp", "unix/RasterWindowContext_unix.h", @@ -72,7 +72,7 @@ skia_component("window") { } if (is_android) { sources += [ "android/GLWindowContext_android.cpp" ] - } else if (is_linux) { + } else if (is_linux || is_freebsd) { sources += [ "unix/GaneshGLWindowContext_unix.cpp", "unix/GaneshGLWindowContext_unix.h", @@ -123,7 +123,7 @@ skia_component("window") { if (skia_enable_graphite) { sources += [ "android/GraphiteVulkanWindowContext_android.cpp" ] } - } else if (is_linux) { + } else if (is_linux || is_freebsd) { sources += [ "unix/GaneshVulkanWindowContext_unix.cpp", "unix/GaneshVulkanWindowContext_unix.h", @@ -178,7 +178,7 @@ skia_component("window") { } if (skia_use_dawn) { - if (is_linux) { + if (is_linux || is_freebsd) { if (dawn_enable_vulkan) { defines = [ "VK_USE_PLATFORM_XCB_KHR" ] libs += [ "X11-xcb" ]patch -p1 < ~/support-freebsd-skia-m142.diff -
返回到根目录:
cd ../..
3. 安装系统依赖
必须安装所有必需的开发工具和库,以确保 cargo build 能够编译 Skia C++ 代码并链接系统库。
sudo pkg install rust ninja python3 gn \
libglvnd libGLU \
png icu harfbuzz freetype2
4. 使用 cargo build 编译 C 库
运行 cargo build 编译 skia-bindings 包。此时,必须使用 环境变量 设置 FreeBSD 系统环境,以确保 build.rs 脚本正确识别。
SKIA_GN_COMMAND="gn" \
SKIA_USE_SYSTEM_LIBRARIES="1" \
SKIA_GN_ARGS="extra_cflags+=[ \"-I/usr/local/include\", \"-I/usr/local/include/harfbuzz\", \"-I/usr/local/include/freetype2\" ]" \
cargo build -p skia-bindings --features "vulkan gl textlayout x11"
SKIA_GN_COMMAND="gn":指示build.rs使用系统安装的gn,而非下载。SKIA_USE_SYSTEM_LIBRARIES="1":使用系统库 (icu, freetype 等),而非 Skia 的内置库,以避免头文件冲突。SKIA_GN_ARGS="...":传递 C++ 编译标志给gn,指定系统头文件路径,例如GL/gl.h、hb.h和ft2build.h。--features "...":激活guiyom工具包所需的 GPU 后端 (Vulkan, GL) 和文本布局功能。
5. C 库 (.a) 的利用
编译成功完成后 (Finished ... 消息确认),将生成两个 C 库文件,供 guiyom 工具包的 Makefile 使用。
-
生成位置:
target/debug/build/skia-bindings-[HASH]/out/skia/(注意:[HASH]值每次构建都不同。) -
产物:
libskia.a(约 101MB):Skia C++ 核心库libskia-bindings.a(约 8.4MB):C API 封装库 (bindings.cpp)
Makefile 示例
guiyom 工具包的 Makefile 必须链接这两个库。C 代码(例如 g-button.c)必须 #include 包含 bindings.cpp 函数声明的 C 头文件(例如 skia-c.h,需要手动创建)。
# cargo build 的中间构建路径
SKIA_OUT_DIR = /path/to/rust-skia-m142/target/debug/build/skia-bindings-[HASH]/out/skia
# C API 头文件路径
SKIA_INC_PATH = -I/path/to/guiyom/skia-c-headers
# 系统依赖库
SYS_LIBS = -lfontconfig -lfreetype -lX11 -lGL \
-lpng -lz -licuuc -lharfbuzz -lpthread -lm
# 最终链接
guiyom: g-button.o other_objects.o ...
$(CXX) g-button.o other_objects.o ... -o guiyom \
-L$(SKIA_OUT_DIR) \
-lskia-bindings \
-lskia \
$(SYS_LIBS)