Guides › Consuming the ICoreBlocks SDK — the public surface, and where it stops
kind: guide#blocks#sdk#cmake#consumer#hello-world#public-surface

Consuming the ICoreBlocks SDK#

How to build against include/icore/ — the two public headers, the icore::sdk CMake target, a hello-world that compiles with no Qt anywhere near it, and the exact point at which a consumer outside this repository is currently stopped.

This guide has a hard edge and says so. The public surface compiles today and does not link: there is no library shipping the definitions. That is measured below, not guessed, and it is the open packaging question on Packaging — what is decided, and the decisions that are open. Everything up to the link step is real and usable now.

What you get#

Two headers and one CMake target:

ThingWhat it is
include/icore/Application.hicore::Application — the process-wide lifetime scope, the event loop, and the only factory for editor windows
include/icore/EditorWindow.hicore::EditorWindow — a top-level editor window; private constructor, Application its only friend
icore::sdk (alias of icore_sdk)an INTERFACE target giving the include root and cxx_std_17, and deliberately no Qt6::* — a consumer of the public surface must never need Qt to compile against it

Invariants a consumer must respect#

  • Construct exactly one Application, at the top of main, before anything else in the SDK is touched, and pass main's own argc/argv — they are consumed by reference and must outlive it.
  • Every window is destroyed before the Application. Declaring the Application first gets this right by reverse destruction order.
  • Text crossing the boundary is UTF-8 std::string / std::string_view.
  • The caller owns the loop. run() is exactly while (isRunning()) tick(); return exitCode();. Use the primitives when you have your own clock; never expect the SDK to block in a loop it controls.
  • Neither type is copyable or movable. They are scopes and handles.

The CMake you write#

cmake_minimum_required(VERSION 3.21)
project(hello_blocks CXX)

add_executable(hello_blocks main.cpp)
target_link_libraries(hello_blocks PRIVATE icore::sdk)   # include root + C++17, no Qt

icore::sdk is defined in this tree's CMakeLists.txt and its header list is written out by hand rather than globbed, so adding a public header is a deliberate act that shows up in review.

Hello-world#

main.cpp — the usage contract from Application.h, verbatim:

#include <icore/Application.h>
#include <icore/EditorWindow.h>

int main(int argc, char** argv) {
    icore::Application app(argc, argv);
    auto editor = app.createEditorWindow();
    editor->setTitle("ICoreBlocks");
    editor->show();
    return app.run();
}

It compiles with no Qt on the include path at all — which is the toolkit-free contract, measured rather than asserted (2026-08-16, commit ccf005c8):

c++ -std=gnu++17 -O2 -arch arm64 -I"$REPO/include" -c main.cpp -o main.o
→ main.o, 1816 bytes

And then it does not link, because nothing ships the definitions:

c++ -arch arm64 main.o -o hello
Undefined symbols for architecture arm64:
  "icore::Application::createEditorWindow()", referenced from: _main in main.o
  "icore::Application::run()", referenced from:               _main in main.o
  "icore::Application::Application(int, char**)", referenced from: _main in main.o
  "icore::Application::~Application()", referenced from:      _main in main.o
  "icore::EditorWindow::show()", referenced from:             _main in main.o

Where a consumer is stopped today, and why#

icore::sdk is INTERFACE and header-only by nature: include/icore/ is declarations only, and the definitions live in the ICoreBlocks executable's own sources — src/ICoreSDK/Shell/Application.cpp implements the surface and the app target links icore::sdk so that it sees the headers the same way an external consumer does.

So the surface is real, the contract is real, and the deliverable is an application rather than a library. To run editor windows today you build and run ICoreBlocks itself; to embed one, the missing piece is a shared or static library exporting these symbols, which is part of the undecided packaging work (Packaging — what is decided, and the decisions that are open).

There is an install() story for the headers already — install(TARGETS icore_sdk EXPORT ICoreBlocksSDKTargets), the two headers into ${CMAKE_INSTALL_INCLUDEDIR}/icore, and the export set as ICoreBlocksSDKTargets.cmake under ${CMAKE_INSTALL_LIBDIR}/cmake/ICoreBlocks with namespace icore::. It is not yet a findable package: no ICoreBlocksConfig.cmake is generated, so find_package(ICoreBlocks) fails even after installing. Do not write it into a consumer's CMake yet.

Traps#

  • Undefined symbols: icore::Application::… is not your mistake. It is the packaging gap above. Check Packaging — what is decided, and the decisions that are open before debugging your link line.
  • A bare --console hangs forever. The application's headless entry point is --console "<command>", always with the command attached.
  • On macOS the binary is inside the bundle. Run build-mac/ICoreBlocks.app/Contents/MacOS/ICoreBlocks; the loose build-mac/ICoreBlocks is stale and will mislead you.
  • quit()/exit() are silent no-ops — the application owns the loop. Route shutdown through requestQuit().
  • Application::liveInstance() is nullable on purpose. A test binary or an embedder may run parts of this codebase with no Application in scope; a caller must be able to ask without asserting.

The platform SDK underneath this one is consumed differently and separately — see Consuming the ICore Platform SDK — CMake, include roots, hello-world. Nothing in the blocks SDK is usable without it.