Guides › Consuming the ICore Platform SDK — find_package, icore::platform, hello-world
kind: guide#platform#sdk#cmake#consumer#hello-world#find_package#install

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 packageBuild 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_NATIVE and ICORE_UI_BACKEND_APPKIT. The first one is required: ICoreEssentials/Text/ICoreStringLiteral.h stops with #error without 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.h for the non-UI families in one line, or any single header you need. The UI families are included header by header, for example ICoreEssentials/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 in ICoreEssentials/System/ICoreVersion.h (ICORE_VERSION_STRING, ICORE_VERSION_HEX, and ICoreVersion at 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=gtk4 against 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: a const char* converts equally well to ICoreString and to std::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, and ICoreDir and ICoreFile are std::filesystem underneath. A scan under ":/" returns an empty result, with no error. Copy the tree onto real disk with ICoreEmbeddedResources::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 with unknown 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, and one_header.cpp above 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:

Librarypkg-config moduleDebian / UbuntuFedora
GTK 4.8 or latergtk4libgtk-4-devgtk4-devel
libcurllibcurllibcurl4-openssl-devlibcurl-devel
ICU, the same major version the package was built againsticu-uclibicu-devlibicu-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.