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:
| Thing | What it is |
|---|---|
include/icore/Application.h | icore::Application — the process-wide lifetime scope, the event loop, and the only factory for editor windows |
include/icore/EditorWindow.h | icore::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 ofmain, before anything else in the SDK is touched, and passmain's ownargc/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 exactlywhile (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
--consolehangs 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 loosebuild-mac/ICoreBlocksis stale and will mislead you. quit()/exit()are silent no-ops — the application owns the loop. Route shutdown throughrequestQuit().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.
Related#
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.