Consuming the ICore Platform SDK#
How to build your own program against the ICore Platform SDK: the package you install, the two lines of CMake you write, what the target gives you, and a hello-world that was built against an installed copy and run, with the output it printed.
The Platform SDK is cross-platform infrastructure behind ICore* types: text,
containers, filesystem, serialization, time, geometry, networking, processes,
crypto, theming and a UI toolkit (windows, widgets, painting, events). No
third-party type appears in its headers. It is its own product. Nothing in it
depends on the ICore Blocks application.
Two ways in, and which one your platform has#
| Platform (UI backend) | Installable package | Build it as part of your project |
|---|---|---|
macOS (appkit) | ✓ since 2026-09-23 | ✓ |
The web (web, Emscripten 6.0.10) | ✓ since 2026-09-24 — see "On the web" below | ✓ |
iPadOS (uikit, iPadOS 17.0+) | ✓ since 2026-09-24, one archive per SDK — see "On iPadOS" below | ✓ |
Android (android, arm64-v8a, API 31+) | ✓ since 2026-09-24 — see "On Android" below | ✓ |
Windows (winui, MSVC, ARM64) | ✓ since 2026-09-25 — see "On Windows" below | ✓ |
Linux (gtk4, GTK 4.8+) | ✓ since 2026-09-25 — see "On Linux" below | ✓ |
The package is the way to consume the SDK. Every backend has one now; the in-tree route at the end of this page is for building the SDK alongside your own sources.
Installing the package#
From the release archive. Extract ICorePlatformSDK-<version>-macos-arm64.tar.gz
anywhere. Its one top-level directory is the prefix.
From the SDK's source tree. That is the ICoreEssentials
repository, whose root is the library. Inside an
ICoreBlocks checkout it sits at ICoreEssentials/, which is the path used
below; a checkout of your own works the same with -S <that checkout>. It
configures on its own, with nothing of the application in the build:
cmake -S ../ICoreEssentials -B <build-dir>
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
The configure reports what it selected and what the package will hold. On a macOS arm64 machine, on 2026-09-24:
-- ICore UI backend: appkit
-- ICore core backend: native
-- ICore UI backend sources: 137 file(s) from ICoreEssentials/UI/Backends/AppKit
-- ICore theme binding sources: 4 file(s) from ICoreEssentials/Theme/AppKitBinding
-- ICore Platform SDK: component platform-sdk installs 279 headers + ICoreVersion.h, icore::platform (appkit + native)
The library is the same one a full build of the source tree makes: the same
sources, compiled with the same flags. The cmake --install line also works on a
full build of the tree, and installs the same files.
That installs exactly this, and nothing belonging to the application:
<prefix>/include/ICoreEssentials/ 318 headers on 2026-09-26: the public surface,
plus the generated System/ICoreVersion.h
<prefix>/lib/libICoreEssentials.a the library (static)
<prefix>/lib/libicore_sodium.a its vendored crypto dependency (static)
<prefix>/lib/libicore_miniz.a its vendored compression dependency (static)
<prefix>/lib/cmake/ICorePlatform/ ICorePlatformConfig.cmake,
ICorePlatformConfigVersion.cmake,
ICorePlatformTargets*.cmake
<prefix>/share/doc/ICorePlatform/ third-party notices (libsodium's and miniz's licences)
Twelve of them are detail headers. They are installed only because a public header includes them (container template bodies, and a few portable cores a public class names), and your code never includes one directly. The backend implementation headers are not installed at all.
libsodium ships as an archive because a static library's own dependency is
still needed when your program links. Its headers do not ship, and you never
include, find or link it by name: icore::platform brings it along.
The CMake you write#
cmake_minimum_required(VERSION 3.21)
project(hello_platform CXX)
find_package(ICorePlatform 1.0 REQUIRED)
add_executable(hello_platform main.cpp one_header.cpp)
target_link_libraries(hello_platform PRIVATE icore::platform)
Configure it with the prefix you installed to:
cmake -S . -B build -DCMAKE_PREFIX_PATH=<prefix>
cmake --build build
icore::platform carries everything else:
- the include root, so
#include "ICoreEssentials/..."resolves; - C++17;
- the backend definitions the library was compiled with,
ICORE_CORE_BACKEND_NATIVEandICORE_UI_BACKEND_APPKIT. The first one is required:ICoreEssentials/Text/ICoreStringLiteral.hstops with#errorwithout it. The second one is there for your own#if; - the system frameworks and libraries it links: AppKit, CoreGraphics,
CoreImage, CoreText, CoreVideo, QuartzCore, UniformTypeIdentifiers,
UserNotifications, CoreFoundation, Foundation, Security, IOKit and
libicucore, all from macOS itself.
Invariants a consumer can rely on#
- Every public header compiles on its own. Include
ICoreEssentials/ICoreEssentials.hfor the non-UI families in one line, or any single header you need. The UI families are included header by header, for exampleICoreEssentials/UI/Windows/ICoreWindow.h. - The version is compatible within a major version.
find_package(ICorePlatform 1.0)accepts any 1.x and refuses 2.x. The stamp is inICoreEssentials/System/ICoreVersion.h(ICORE_VERSION_STRING,ICORE_VERSION_HEX, andICoreVersionat run time). - Within a major version, nothing you compiled against changes shape. No public class gains or loses a virtual function, a base or a data member, and no public function is removed or renamed. Both are measured before a release.
- The backend is part of the package. One installed package serves one backend, and a project that asks for another is refused when it configures (see the traps).
Hello-world#
Two files. main.cpp uses the umbrella, the version stamp and a real window:
#include "ICoreEssentials/ICoreEssentials.h"
#include "ICoreEssentials/System/ICoreVersion.h"
#include "ICoreEssentials/UI/System/ICoreApplication.h"
#include "ICoreEssentials/UI/Windows/ICoreWindow.h"
#include <cstdio>
bool oneHeaderTranslationUnit(); // one_header.cpp
int main(int argc, char** argv) {
const ICoreString greeting = ICoreString("hello, ") + ICoreString("platform SDK");
std::printf("%s %s\n", greeting.toUpper().toStdString().c_str(), ICORE_VERSION_STRING);
ICoreApplication app(argc, argv);
ICoreWindow window;
window.setWindowTitle(ICoreString("ICore Platform SDK consumer"));
window.resize(320, 200);
window.show();
for (int i = 0; i < 50 && !window.isVisible(); ++i)
app.tick(10);
const bool shown = window.isVisible();
std::printf("window visible: %s\n", shown ? "yes" : "no");
window.close();
app.tick(0);
const bool alone = oneHeaderTranslationUnit();
std::printf("one-header TU: %s\n", alone ? "ok" : "FAILED");
return shown && alone ? 0 : 1;
}
one_header.cpp is a source file that includes one public header and
nothing else:
#include "ICoreEssentials/Filesystem/ICorePath.h"
bool oneHeaderTranslationUnit() {
const ICorePath path(ICoreString("/tmp/icore/notes.txt"));
return path.fileName().toStdString() == "notes.txt";
}
Built against a package installed from commit 658a45152 (macOS arm64,
Apple clang, appkit) with the CMake above, and run:
HELLO, PLATFORM SDK 1.0.3
window visible: yes
one-header TU: ok
The program exits 0. The window appears for a moment and closes itself. (1.0.3 was the app's number, which the SDK shared until 2026-09-28. The SDK now numbers itself from 1.0.0 (ICore Platform SDK — what it is and what it gives you), so a package cut today prints 1.0.0.)
Following the theme#
ICoreThemeManager::onThemeChanged() says that the theme changed. To keep a
widget's own colours right for as long as the widget lives, subscribe through
ICoreThemeFollow instead:
#include "ICoreEssentials/UI/System/ICoreThemeFollow.h"
class Swatch : public ICoreWidget {
public:
Swatch() {
// Runs now, and again after every theme change, until this widget is destroyed.
ICoreThemeFollow::subscribe(this, [this] { applyTheme(); });
}
private:
void applyTheme();
};
subscribe(widget, fn) runs fn once at once, then after every theme change,
and stops when the widget is destroyed, so fn may capture this. A scene item
works the same way while it is in a scene. repaintOnThemeChange(widget) covers
the common case of drawing code that reads the theme itself. withAlpha(color,
alpha) and inkOn(surface) are the two colour helpers subscribers usually need:
the second returns the theme's light or dark text colour, whichever reads on
surface.
A tray icon#
ICoreTrayIcon puts an icon in the system's status area, with a tooltip and a
menu that opens when it is clicked:
#include "ICoreEssentials/UI/System/ICoreTrayIcon.h"
ICoreTrayIcon tray; // hidden until setVisible(true)
tray.setIconPath(":/icons/app.svg"); // or a file path; SVG or any image
tray.setToolTip("Syncing");
tray.setMenu(&menu); // an ICoreMenu that outlives the icon
tray.onActivated().connect(scope, [] { /* clicked */ });
tray.setVisible(true);
ICoreTrayIcon::isSupported() is true on macOS, where the icon is a menu-bar
status item drawn as a template image, so it follows the bar's light or dark
appearance. It is true on Windows too, where the icon sits in the taskbar's
notification area at the small-icon size, drawn as it is. There, a left click, a
right click and the keyboard's selection all fire onActivated(), and the menu
opens at the cursor. A new icon usually lands in the overflow flyout until the
user pins it: Windows, not the program, decides that. If Explorer restarts, the
icon is put back. On Linux it is true as well: the icon is a
StatusNotifierItem, the D-Bus protocol that Plasma, waybar, xfce4-panel and
GNOME's AppIndicator extension draw from. The desktop draws the menu too, in its
own style, from your ICoreMenu's rows, so it looks native. A row clicked there
runs as a click on the painted row would, and the menu is updated while it is
open if its rows change. A desktop that runs no such host (plain GNOME without
the extension) does not show the icon. Nothing fails; there is just nothing to
draw it. On the web, iPadOS and Android isSupported() is false. There, every call
is accepted and does nothing, so the same code builds everywhere without an
#if.
A system notification#
ICoreSystemNotification posts one notification through the operating system:
Notification Center on macOS and iPadOS, a toast on Windows, the desktop's
notification service on Linux, and a browser notification on the web.
#include "ICoreEssentials/UI/System/ICoreSystemNotification.h"
ICoreSystemNotification done;
done.setTitle("Export finished");
done.setBody("controller.vhd -- 4,812 lines");
done.setShownHandler([] { /* the system accepted it */ });
done.setFailedHandler([](const std::string& why) { /* turned off, or no service */ });
done.setActivatedHandler([] { /* the user clicked it */ });
done.show(); // show() again replaces it in place
Everything is asynchronous, and the handlers run on the main thread, so an
ICoreApplication must be running. show() asks the user for permission the
first time, so you do not have to. Call
ICoreSystemNotification::requestPermission() yourself only when you want to
choose the moment. On the web, ask from a click handler, because a browser
ignores a request made without a click. withdraw() removes the notification.
Destroying the object leaves the notification on screen, but its clicks are no
longer reported.
⚠ On macOS and iPadOS, only an application bundle can post notifications.
A bare command-line executable gets Permission::Unavailable, and show()
fails with a message that says so. macOS also refuses a bundle that sits under
/tmp.
Traps#
Asking for a different backend is refused at configure time, on purpose. The backend is compiled into the library, so a project that sets
ICORE_UI_BACKEND=gtk4against the macOS package gets this, verbatim, before anything is built:``` CMake Error at CMakeLists.txt:14 (find_package): Found package configuration file:
<prefix>/lib/cmake/ICorePlatform/ICorePlatformConfig.cmake
but it set ICorePlatform_FOUND to FALSE so package "ICorePlatform" is considered to be NOT FOUND. Reason given by package:
ICORE_UI_BACKEND is 'gtk4', but this ICore Platform SDK was built for 'appkit'. The backend is compiled into the library: install the package built for 'gtk4', or drop the setting. ```
The package config applies the same check to the operating system it was built for.
- Asking for the next major version is refused too.
find_package(ICorePlatform 2.0)against a 1.x package reports "Could not find a configuration file for package "ICorePlatform" that is compatible with requested version "2.0"" and lists the 1.0.3 it rejected. - A raw string literal is ambiguous where a type takes both spellings.
ICorePath path("/tmp/x.txt")does not compile: aconst char*converts equally well toICoreStringand tostd::string, and both constructors exist. Say which:ICorePath(ICoreString("/tmp/x.txt")). The error says "ambiguous" and names neither of the types you were thinking about. - Never point a filesystem type at a
":/"path. Files compiled into a binary live in the toolkit's own virtual namespace, andICoreDirandICoreFilearestd::filesystemunderneath. A scan under":/"returns an empty result, with no error. Copy the tree onto real disk withICoreEmbeddedResources::copyTreeToDisk()and read the copy. - ⚠ Fixed on 2026-09-23. It is kept for anyone on an older checkout.
#include "ICoreEssentials/Filesystem/ICorePath.h"alone did not compile. It failed withunknown type name 'ICoreDateTime'at line 126, because the source tree's own build delivers the umbrella through a precompiled header, so no header had ever needed to include its siblings. Every public header now includes what it names, andone_header.cppabove is the proof.
On the web#
The web package is ICorePlatformSDK-<version>-web-wasm32.tar.gz, or it is built
from source with the Emscripten toolchain:
source ~/emsdk/emsdk_env.sh # Emscripten 6.0.10, the version the package records
emcmake cmake -S ../ICoreEssentials -B <build-dir>
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
Your project is configured with the same toolchain. Name the prefix as a find root too, because a cross-compiling toolchain looks for packages inside its own sysroot and nowhere else:
emcmake cmake -S <your-project> -B <dir> \
-DCMAKE_PREFIX_PATH=<prefix> -DCMAKE_FIND_ROOT_PATH=<prefix>
The CMake is the same two lines, plus one that makes your program a page:
find_package(ICorePlatform 1.0 REQUIRED)
add_executable(hello_platform main.cpp one_header.cpp)
target_link_libraries(hello_platform PRIVATE icore::platform)
icore_platform_web_page(hello_platform TITLE "Hello") # defined by the package on the web
icore_platform_web_page() links your program into the SDK's page shell. That
shell gives the UI its canvas, shows an error panel if the program aborts, passes
?arg= values from the URL as argv, and prints icore-exit: <code> to the
console when main() returns. It also sets the runtime options the UI needs:
growable memory, a page environment, and the runtime exiting when main() does.
The result is hello_platform.html beside its .js and .wasm. Serve the
directory over HTTP; a page opened from file:// cannot load its .wasm.
Where ICU and libpng come from. On the web they are Emscripten's own ports,
not libraries in the package. icore::platform asks for them at your link
(-sUSE_ICU=1 -sUSE_LIBPNG=1), together with the fetch and IndexedDB file-system
options the SDK uses, so you install nothing yourself: Emscripten downloads and
caches the ports the first time. icore::platform also adds -fwasm-exceptions
to your compile. That is required, not a preference: the library throws and
catches exceptions, and code compiled with Emscripten's default exception mode
would abort at the first exception that crosses into it.
The Emscripten version is part of the package. A different Emscripten is a different compiler and a different set of system libraries, so the package refuses it at configure time:
this ICore Platform SDK was built with Emscripten 6.0.10, and this project's
toolchain is '<yours>'. Activate Emscripten 6.0.10 (emsdk install/activate),
or install the package built for yours.
The hello-world above, built against the installed web package and run in
headless Chrome on 2026-09-24, printed the same three lines as on macOS, then
icore-exit: 0.
On iPadOS#
There is one package per SDK: ICorePlatformSDK-<version>-ios-arm64-simulator.tar.gz
for the simulator and ICorePlatformSDK-<version>-ios-arm64.tar.gz for devices. The
two libraries are different binaries, so install the one you build for. From source,
with Xcode selected for the command only (DEVELOPER_DIR), not for the whole machine:
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
cmake -S ../ICoreEssentials -B <build-dir> -DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=arm64
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
Your project is configured for the same SDK. Name the prefix as a find root too, because CMake's iOS platform looks for packages inside the SDK and nowhere else:
cmake -S <your-project> -B <dir> -DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 \
-DCMAKE_PREFIX_PATH=<prefix> -DCMAKE_FIND_ROOT_PATH=<prefix>
UIKit runs nothing that is not installed as an application bundle, so the package gives you the function that makes one:
find_package(ICorePlatform 1.0 REQUIRED)
add_executable(hello_platform main.cpp one_header.cpp)
target_link_libraries(hello_platform PRIVATE icore::platform)
icore_platform_uikit_bundle(hello_platform BUNDLE_ID com.example.hello NAME "Hello")
It fills the SDK's Info.plist template (a scene manifest, a launch screen, your
deployment target as MinimumOSVersion), links the frameworks the UIKit backend
needs, and signs the bundle after the link. A simulator bundle is signed ad hoc. A
device bundle needs your identity and profile, passed as
-DICORE_IOS_CODE_SIGN_IDENTITY="Apple Development: …" and
-DICORE_IOS_PROVISIONING_PROFILE=<file>.mobileprovision; without them the configure
stops and says which one is missing.
The package refuses what it cannot serve, at configure time. The two iPadOS refusals, as CMake printed them for the simulator package on 2026-09-24 (the prefix path shortened):
CMake Error at CMakeLists.txt:14 (find_package):
Found package configuration file:
<prefix>/lib/cmake/ICorePlatform/ICorePlatformConfig.cmake
but it set ICorePlatform_FOUND to FALSE so package "ICorePlatform" is
considered to be NOT FOUND. Reason given by package:
this ICore Platform SDK was built for iphonesimulator, and this project
builds for iphoneos
(CMAKE_OSX_SYSROOT='/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS27.0.sdk').
Install the package built for iphoneos.
this ICore Platform SDK was compiled for iPadOS 17.0 and later, and this
project's CMAKE_OSX_DEPLOYMENT_TARGET is '16.0'. Configure with
-DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 or higher.
The hello-world above, built against the installed simulator package and run on the iPad simulator on 2026-09-24, printed the same three lines as on macOS and exited 0.
On Android#
The Android package is ICorePlatformSDK-<version>-android-arm64.tar.gz, for the
arm64-v8a ABI and Android API 31 and later. From source, with the Android NDK:
cmake -S ../ICoreEssentials -B <build-dir> \
-DCMAKE_TOOLCHAIN_FILE=<ndk>/build/cmake/android.toolchain.cmake
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
The NDK's defaults here are arm64-v8a and API 31. Your project uses the same
toolchain, ABI and API level, and names the prefix as a find root too, because the
NDK's toolchain looks for packages inside its own sysroot and nowhere else:
cmake -S <your-project> -B <dir> \
-DCMAKE_TOOLCHAIN_FILE=<ndk>/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-31 \
-DCMAKE_PREFIX_PATH=<prefix> -DCMAKE_FIND_ROOT_PATH=<prefix>
An Android app has no main(): a Java Activity starts the process and loads your
code as a shared library. The package ships that Activity and the function that
packages your library with it into an installable APK:
find_package(ICorePlatform 1.0 REQUIRED)
add_library(hello_platform SHARED main.cpp one_header.cpp) # a library, not an executable
target_link_libraries(hello_platform PRIVATE icore::platform)
icore_platform_android_apk(hello_platform_apk LIBRARY hello_platform
PACKAGE com.example.hello LABEL "Hello")
Your main(int argc, char** argv) stays as it is: icore_platform_android_apk()
compiles the library with main renamed to the entry the Activity calls, so the
same source runs on a desktop and here, with the same arguments and the same exit
status. Build the APK target (cmake --build <dir> --target hello_platform_apk) to
get hello_platform_apk.apk, signed with a debug key that is good for
adb install and nothing else. Standard output and standard error go to logcat, under
the tags ICoreBlocks-out and ICoreBlocks-err. The APK is packaged with the Android
SDK's own tools (build-tools 36.1.0, platform 36, javac, keytool, zip), not
Gradle. If one is missing, the configure says which one and skips the APK target.
The package refuses another ABI and a lower API level, at configure time. As CMake printed them for this package on 2026-09-24:
this ICore Platform SDK was built for the arm64-v8a ABI, and this project
builds for 'x86_64'. Configure with -DANDROID_ABI=arm64-v8a, or install
the package built for yours.
this ICore Platform SDK was compiled for Android API 31 and later, and this
project's ANDROID_PLATFORM is 'android-30'. Configure with
-DANDROID_PLATFORM=android-31 or higher.
The hello-world above, built against the installed package and run on the Android emulator on 2026-09-24, printed the same three lines as on macOS and exited 0.
On Windows#
The Windows package is ICorePlatformSDK-<version>-windows-arm64.tar.gz, built with MSVC for ARM64 and the Release C
runtime. From source, in a Visual Studio developer prompt (or after
vcvarsarm64.bat):
cmake -S ../ICoreEssentials -B <build-dir> -G Ninja -DCMAKE_BUILD_TYPE=Release -DICORE_UI_BACKEND=winui
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
A Windows program built on the SDK is a WinUI program, and it needs two things beside its code: the Windows App SDK runtime next to the executable (the SDK is self-contained, so it never looks for one installed on the machine), and a manifest that registers the runtime's classes. The package ships both, and one function applies them:
find_package(ICorePlatform 1.0 REQUIRED)
add_executable(hello_platform main.cpp one_header.cpp)
target_link_libraries(hello_platform PRIVATE icore::platform)
icore_platform_winui_app(hello_platform) # the manifest, the runtime, the C++ runtime
icore_platform_winui_app() embeds the manifest (so it switches the linker's own
manifest off). After every build it copies the runtime beside the executable, together
with the C++ runtime of the compiler that built it. Ship that whole directory. It
asks the machine for nothing beyond Windows itself, 10 version 1903 or later (for the
system ICU). That was measured by where each DLL loaded from, below. It has not yet been
tried on a freshly installed machine. The Windows App SDK's
licence and notice are in share/doc/ICorePlatform/third-party/windows-app-sdk/. If
your program has a manifest of its own, merge the package's template
(lib/cmake/ICorePlatform/ICorePlatformApp.manifest.in) into it rather than calling the
function.
Build your project with MSVC for the same architecture, as Release or RelWithDebInfo. A Debug build uses the other C runtime, and the two do not link together, so the package refuses it at configure time. As CMake printed it for this package on 2026-09-25:
this ICore Platform SDK's library uses the Release C runtime, and this
project's CMAKE_BUILD_TYPE 'Debug' uses the Debug one. The two do not link
together (LNK2038): build this project as Release or RelWithDebInfo for a
Release package, as Debug for a Debug one.
The hello-world above, built against the extracted archive in a directory with no path to this repository, printed the same three lines as on macOS and exited 0. Every App SDK DLL it loaded came from beside it, and none from a framework package installed on the machine.
On Linux#
The Linux package is ICorePlatformSDK-<version>-linux-<arch>.tar.gz, built with
GCC against the gtk4 backend. From source:
cmake -S ../ICoreEssentials -B <build-dir> -DCMAKE_BUILD_TYPE=Release -DICORE_UI_BACKEND=gtk4
cmake --build <build-dir> --target ICoreEssentials
cmake --install <build-dir> --component platform-sdk --prefix <prefix>
The package bundles no third-party library. It links three that every Linux distribution ships, and your machine needs their development packages:
| Library | pkg-config module | Debian / Ubuntu | Fedora |
|---|---|---|---|
| GTK 4.8 or later | gtk4 | libgtk-4-dev | gtk4-devel |
| libcurl | libcurl | libcurl4-openssl-dev | libcurl-devel |
| ICU, the same major version the package was built against | icu-uc | libicu-dev | libicu-devel |
find_package(ICorePlatform) finds all three through your own pkg-config and
links them for you, so your CMakeLists.txt names nothing but icore::platform.
Your program then needs only the runtime libraries, which a desktop has
already. A missing one stops the configure, and the message names the package to
install. As CMake printed it on 2026-09-25, on a machine with no GTK:
this ICore Platform SDK links the system's gtk4>=4.8, and pkg-config does
not find it here. Install its development package (Debian/Ubuntu:
libgtk-4-dev; Fedora: gtk4-devel), or put its .pc directory on
PKG_CONFIG_PATH.
ICU is the one to watch. Its symbols carry its major version (u_charType_78),
so a package built against ICU 78 does not link against ICU 76. Use the package
built on your distribution release, or build it from source as above. The package
refuses a mismatch at configure time rather than at link:
this ICore Platform SDK was built against ICU 78, and pkg-config finds ICU
76.1 here. ICU's symbols carry its major version, so the library does not
link against another one: install the package built for ICU 76, or point
PKG_CONFIG_PATH at an ICU 78.
The hello-world above, built against the extracted archive in a directory with no
path to this repository, with PATH holding only /usr/bin and /bin, printed
the same three lines as on macOS, opened its window and exited 0 (Ubuntu 26.04,
aarch64, GTK 4.22, ICU 78, 2026-09-25). No text file in the archive names the
machine that built it.
Building the SDK as part of your project#
Every platform has a package, but the SDK can also be consumed from inside its own source tree. Configure and build that tree as usual, and add your program as one more target of that CMake project, linking the library target:
add_executable(hello_platform main.cpp one_header.cpp)
target_include_directories(hello_platform PRIVATE "${CMAKE_SOURCE_DIR}/src")
target_link_libraries(hello_platform PRIVATE icore::platform) # the ICoreEssentials target
The include root is the directory that contains ICoreEssentials/, which in
the source tree is src/. Inside the tree the target deliberately propagates no
include directory of its own. Its own root is private and firewalled, which is
what keeps the SDK from depending on anything outside it, so you name src/
yourself. The backend is chosen when the tree is configured: ICORE_UI_BACKEND
is appkit, gtk4 or winui on the three desktops, each the default on its own
platform, and web, uikit or android when the toolchain is Emscripten, iOS or
the Android NDK, each the default for its toolchain. ICORE_CORE_BACKEND is
native. No Qt is involved on any of them.
The SDK's own directory also configures without the rest of the tree
(cmake -S ../ICoreEssentials, above), which builds the library alone; built
that way, you still name its src/ root yourself.
What is decided, and what is not#
Decided on 2026-09-23: the SDK ships as a static library only, with
default symbol visibility, as one package (icore::platform, covering the
core, UI and theme families together). A prebuilt package is planned for
every backend, and every one has one today: macOS, the web, iPadOS, Android,
Windows and Linux.
Still open: whether the SDK's global ICore* types move into a namespace (see
Packaging — what is decided, and the decisions that are open). Within the 1.x line that could only be added alongside the
current names, never instead of them, because of the rules above.