How to Generate Skia C API Library on FreeBSD Using rust-skia
Introduction: The C++ ABI Incompatibility Problem
The previous post(How to compile Skia on FreeBSD) covered how to directly compile the Skia C++ library (libskia.a) using gn and ninja. This method is valid for running examples integrated within the gn build system, such as viewer.
However, when attempting to link this libskia.a library into an external C/C++ project using Makefile or CMake, the C++ ABI (Application Binary Interface) incompatibility problem arises.
- Cause: Skia’s C++ build system (
gn) defines preprocessor flags (-D...) likeskia_use_vulkan=true. Skia header files, such asDisplayParams.h, dynamically change the structure’s memory layout (size) based on these flags. - Issue: A C project compiled with
Makefiledoes not know the flags used bygn. This results in an ABI mismatch where the size of the same structure differs between the library (libskia.a) and the C project code, causingInvalid readorSIGSEGV(memory corruption).
The C API Solution Utilizing rust-skia
The rust-skia project provides a C API Bridge to solve this ABI problem. The rust-skia’s cargo build process performs the following steps:
- It compiles the Skia C++ core (
libskia.a) usinggn. - It captures all the C++ compilation flags used by
gn. - It compiles the C API wrapper (
bindings.cpp) using the identical flags captured in step 2 to producelibskia-bindings.a.
Through this process, libskia.a and libskia-bindings.a become an ABI-compatible set of C libraries.
This article explains how to utilize the rust-skia build system to generate the Skia C libraries (libskia.a, libskia-bindings.a) for C toolkit development.
1. Download the rust-skia Source Code
You must use git to clone the source code, including the skia submodule.
# Clone based on the m142 (0.90.0) version tag.
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. Patch the Skia C++ Source Code
You must apply the FreeBSD support patch to the skia-bindings/skia submodule, which is the target for the rust-skia build script.
-
Navigate to the Skia Submodule Directory:
cd skia-bindings/skia -
Apply the Patch: (Assuming the FreeBSD support patch file is at
~/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 -
Return to the Root Directory:
cd ../..
3. Install System Dependencies
Install all essential development tools and libraries so that cargo build can compile the Skia C++ code and link against system libraries.
sudo pkg install rust ninja python3 gn \
libglvnd libGLU \
png icu harfbuzz freetype2
4. Compile C Libraries with cargo build
Run cargo build to compile the skia-bindings package. Environment variables must be used during this step to ensure the build.rs script correctly recognizes the FreeBSD system environment.
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": Instructsbuild.rsto use the system-installedgninstead of downloading it.SKIA_USE_SYSTEM_LIBRARIES="1": Uses system libraries (icu, freetype, etc.) instead of Skia’s vendored libraries, which prevents header conflicts.SKIA_GN_ARGS="...": Passes C++ compilation flags togn, specifying system header paths likeGL/gl.h,hb.h, andft2build.h.--features "...": Activates the GPU backends (Vulkan, GL) and text layout features required for theguiyomtoolkit.
5. Utilizing the C Libraries (.a)
Once compilation is successful (Finished ... message confirmation), two C library files will be generated for use in the guiyom toolkit’s Makefile.
-
Output Location:
target/debug/build/skia-bindings-[HASH]/out/skia/(Note: The[HASH]value changes with each build.) -
Artifacts:
libskia.a(approx. 101MB): The core Skia C++ library.libskia-bindings.a(approx. 8.4MB): The C API wrapper library (bindings.cpp).
Makefile Example
The guiyom toolkit’s Makefile must link both libraries. C code (e.g., g-button.c) must #include a C header file (e.g., skia-c.h, which needs to be manually created) containing the function declarations from bindings.cpp.
# Intermediate build path from cargo build
SKIA_OUT_DIR = /path/to/rust-skia-m142/target/debug/build/skia-bindings-[HASH]/out/skia
# C API header path
SKIA_INC_PATH = -I/path/to/guiyom/skia-c-headers
# System dependencies libraries
SYS_LIBS = -lfontconfig -lfreetype -lX11 -lGL \
-lpng -lz -licuuc -lharfbuzz -lpthread -lm
# Final Linkage
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)