Get started · Linux & Windows x86_64

From nothing to a running app in about ten minutes

One download, four files, one swift build. You do not need an engine checkout, depot_tools, ninja, or 40 GB of disk — the SDK bundle carries the Flutter engine binaries with it. Every command below was run start to finish on a clean machine before this page was written.

The walkthrough is written for Linux. Windows takes the same four steps with a different bundle and host — each step carries a On Windows box with the difference, and they are the only differences.

Before you start

What you need

  • Linux x86_64 with a graphical session (Wayland or X11). Ubuntu 26.04 is what we test on; anything with a recent glibc should work.
  • Swift 6.2 or newer — from swift.org or swiftly. Tested on 6.2.4 and 6.3.3; on 6.3 you can drop two flags from the manifest below, so it is the one to pick if you have a choice.
  • GTK 3 and a few system libraries, which the engine links against:
    sudo apt install libgtk-3-dev libegl-dev libgles-dev libdrm-dev \
                     libgbm-dev libxkbcommon-dev libepoxy-dev

On Windows you need instead:

  • Windows 10 or 11, x86_64. Tested on Windows 11.
  • Swift 6.2 for Windows from swift.org, and the Visual Studio 2022 build tools it depends on (tested with 17.14 and the 10.0.22621 Windows SDK).
  • No system libraries to install — the engine's Win32 embedder uses what ships with Windows.
Step one

1Install the SDK

The bundle is the framework plus the engine binaries it links against, in one tree. Unpack it anywhere — /opt is a good default.

curl -LO https://github.com/starling-build/starling/releases/download/sdk-v0.2.0/starling-sdk-linux-x86_64.tar.gz
sudo tar -xzf starling-sdk-linux-x86_64.tar.gz -C /opt/

That gives you:

/opt/starling-sdk-linux-x86_64/
  Package.swift        # the SDK, as a SwiftPM package
  Sources/             # the framework: widgets, rendering, gestures…
  Examples/            # counter, todos, calendar, the demo app
  engine/lib/          # libflutter_engine.so + the embedders
  engine/share/        # icudtl.dat, flutter_assets

Verify the download if you like — the release carries a SHA256SUMS file next to the tarball:
curl -sL …/SHA256SUMS | sha256sum -c -

On Windows, take the zip and unpack it to the drive root:

curl.exe -LO https://github.com/starling-build/starling/releases/download/sdk-v0.2.0/starling-sdk-windows-x86_64.zip
Expand-Archive starling-sdk-windows-x86_64.zip -DestinationPath C:\

Same tree, with engine/lib/ holding flutter_engine.dll and flutter_windows.dll — each beside its .lib import library, because on Windows the link step needs the import library and the run needs the DLL.

Step two

2Create the package

An ordinary SwiftPM executable that depends on the SDK by path. Three products: Flutter is the framework, FlutterSwiftBridge carries the dart:ui types (Color, Offset, Rect), and FlutterGTK is the window host.

mkdir -p hello-starling/Sources/HelloStarling && cd hello-starling
Package.swift
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "HelloStarling",
    dependencies: [
        .package(path: "/opt/starling-sdk-linux-x86_64"),
    ],
    targets: [
        .executableTarget(
            name: "HelloStarling",
            dependencies: [
                .product(name: "Flutter", package: "starling-sdk-linux-x86_64"),
                .product(name: "FlutterSwiftBridge", package: "starling-sdk-linux-x86_64"),
                .product(name: "FlutterGTK", package: "starling-sdk-linux-x86_64"),
            ],
            swiftSettings: [
                // Required: the framework is built with C++ interop, and
                // SwiftPM does not inherit it from a dependency.
                .interoperabilityMode(.Cxx),
                // Swift 6.2 on Ubuntu 26.04 only. DELETE these two lines on
                // Swift 6.3+ — see "If it does not build".
                .unsafeFlags(["-Xcc", "-D_GLIBCXX_MATH_H",
                              "-Xcc", "-include", "-Xcc", "/usr/include/math.h"]),
            ]
        ),
    ]
)

The package name is the directory name. A path dependency takes its name from the folder, so it is package: "starling-sdk-linux-x86_64" — not "starling-sdk". Rename the folder and you rename the package.

On Windows, three changes to that manifest:

  • the path and package name become "C:/starling-sdk-windows-x86_64" and "starling-sdk-windows-x86_64" — forward slashes are fine, and the same directory-name rule applies;
  • FlutterWin32 replaces FlutterGTK. It is the engine's own Win32 embedder, so windowing, input, IME and accessibility come from the same code path real Flutter Windows apps use;
  • drop the .unsafeFlags line entirely. Those two -Xcc flags work around an Ubuntu 26.04 glibc clash and mean nothing on Windows. .interoperabilityMode(.Cxx) is still required.
Step three

3Write the app

If you have written Flutter, this will look familiar: a StatefulWidget, a State that owns the counter, and setState to change it. The only unfamiliar part is the last three lines — GTKHost opens a window and mounts your widget in it.

Sources/HelloStarling/main.swift
import Flutter
import FlutterGTK
import FlutterSwiftBridge

final class Counter: StatefulWidget {
    override func createState() -> State<StatefulWidget> { CounterState() }
}

final class CounterState: State<StatefulWidget> {
    private var taps = 0

    override func build(_ context: any BuildContext) -> Widget {
        return ColoredBox(color: Color(0xFFF5F5F7)) {
            Center {
                Column(mainAxisAlignment: .center) {
                    Text("Hello from the Starling SDK",
                         style: TextStyle(color: Color(0xFF1B1B1F), fontSize: 30))
                    SizedBox(height: 18)
                    Text("taps: \(taps)",
                         style: TextStyle(color: Color(0xFF6B6B75), fontSize: 20))
                    SizedBox(height: 24)
                    GestureDetector(
                        onTap: { [weak self] in self?.setState { self?.taps += 1 } },
                        child: ColoredBox(color: Color(0xFF2F6DF6)) {
                            Padding(padding: EdgeInsets(horizontal: 22, vertical: 12)) {
                                Text("Tap me",
                                     style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 18))
                            }
                        }
                    )
                }
            }
        }
    }
}

guard let host = GTKHost(width: 720, height: 420, title: "Hello Starling") else {
    fatalError("no display — run inside a Wayland or X11 session")
}
host.mountWidget { Directionality(textDirection: .ltr, child: Counter()) }
host.run()

Two Swift-isms if you are coming from Dart. Every container takes a trailing closure, so Column { … } replaces Column(children: [ … ]) and lets you write if and for directly inside the tree. And Dart's named constructors become plain initialisers: EdgeInsets.symmetric(horizontal:vertical:) is EdgeInsets(horizontal:vertical:) here.

On Windows, two lines change — the import and the host. The widget code above is identical, character for character:

import FlutterWin32              // instead of FlutterGTK

guard let host = Win32Host(width: 720, height: 420, title: "Hello Starling") else {
    fatalError("could not create the window")
}
Step four

4Build and run

The first build compiles the framework and takes a few minutes; later builds of your own code take seconds.

swift build -c release

Before running, the engine needs its data — icudtl.dat and flutter_assets — in a data/ folder beside the executable. This is the standard Flutter bundle layout, and the one thing that is easy to miss:

mkdir -p .build/release/data
ln -sf /opt/starling-sdk-linux-x86_64/engine/share/icudtl.dat      .build/release/data/
ln -sf /opt/starling-sdk-linux-x86_64/engine/share/flutter_assets  .build/release/data/

./.build/release/HelloStarling
A window titled Hello Starling showing the text 'Hello from the Starling SDK', a tap counter reading 2, and a blue 'Tap me' button.
The app above, after two clicks — built from the published bundle on a machine with no engine checkout.

Shipping it

To run on a machine without the SDK installed, put the engine libraries and the data beside your binary and give it an rpath of $ORIGIN:

mkdir -p dist/data
cp .build/release/HelloStarling                          dist/
cp /opt/starling-sdk-linux-x86_64/engine/lib/*.so        dist/
cp -r /opt/starling-sdk-linux-x86_64/engine/share/*      dist/data/

On Windows, use the bundle's build script for the first build:

C:\starling-sdk-windows-x86_64\tools\build-windows.ps1 -PackagePath . -Configuration release

A cold Clang module cache makes the first C++-interop compilation fail inside MSVC's own <xmemory> with no matching function for call to 'construct_at'. That is a toolchain bug, not your package: the failing run still writes a usable module, so the same command run again gets further, and it needs one pass per interop configuration. The script loops only while that exact signature appears, so a real build error still fails immediately. Once the cache is warm, plain swift build is fine.

Windows has no rpath, so the DLLs must sit beside the executable — this is the staging step, and the equivalent of the data/ symlinks above:

copy C:\starling-sdk-windows-x86_64\engine\lib\*.dll        .build\release\
mkdir .build\release\data
copy C:\starling-sdk-windows-x86_64\engine\share\icudtl.dat .build\release\data\
xcopy /E /I C:\starling-sdk-windows-x86_64\engine\share\flutter_assets .build\release\data\flutter_assets

.build\release\HelloStarling.exe

To ship it, copy the same files next to your binary plus the Swift runtime DLLs from %LOCALAPPDATA%\Programs\Swift\Runtimes\<version>\usr\bin. The bundle deliberately does not carry those — they belong to your toolchain, exactly as libswiftCore does on Linux.

Troubleshooting

If it does not build — or does not run

cmath: redefinition of 'acos'

Swift 6.2 on Ubuntu 26.04. The 6.2 toolchain is built for 24.04, and 26.04 pairs glibc 2.43 with libstdc++ 15, which makes Foundation's C shim pull <cmath> twice under C++ interop. The two -Xcc flags in the manifest above are the workaround, and they must be on your own target — SwiftPM does not propagate compiler settings from a dependency, so the SDK setting them internally does nothing for you.

Swift 6.3 does not need them. We measured both: 6.2.4 fails exactly as above without the flags and builds with them; 6.3.3 builds either way. If you are on 6.3 or newer, delete those two lines — they are only clutter there. (There is still no official Ubuntu 26.04 toolchain; 24.04 remains the newest swift.org build, and it works fine on 26.04.)

libflutter_linux_gtk.so: cannot open shared object file

The engine libraries are not being found. The bundle bakes an rpath into your binary pointing at /opt/starling-sdk-linux-x86_64/engine/lib, so this means the SDK moved after you built. Rebuild, or set LD_LIBRARY_PATH to wherever the libraries now live.

The window opens but stays black

The engine could not find its data. Check that .build/release/data/icudtl.dat resolves — a broken symlink looks exactly like this. On a machine with no GPU acceleration (a VM, or a virtual display), also try LIBGL_ALWAYS_SOFTWARE=1.

no display

GTKHost needs a graphical session. Over SSH, forward a display or run inside one — the host is a GTK window, not a framebuffer client. On a Wayland session where GTK picks the wrong backend, GDK_BACKEND=x11 is a useful fallback.

xmemory: no matching function for call to 'construct_at' — Windows

The cold module-cache bug described in step four. It is a Swift/MSVC toolchain bug rather than anything your package did — nothing in the tree includes the headers it fails in. Run the build again, or use tools\build-windows.ps1, which does that for you and stops as soon as the error is a real one.

0xC0000135 / the app exits immediately — Windows

STATUS_DLL_NOT_FOUND, raised before main(), so there is no message — only an exit code. Something the executable links is not beside it and not on PATH: either the two engine DLLs, or the Swift runtime DLLs on a machine without the toolchain. Copy both next to the .exe. The same code from swift build itself means PATH is missing the toolchain's Runtimes\<version>\usr\bin.

Next

Where to go from here

The bundle ships its own Examples/: the Flutter counter, a todo app, a startup-name generator, and a calendar package port with day, week and month views. They are targets of the SDK package, so you can run them in place:

cd /opt/starling-sdk-linux-x86_64
swift run -c release CounterApp

On Windows: cd C:\starling-sdk-windows-x86_64, then swift run -c release CounterApp. The examples find the bundle's engine/share themselves, so they need no staging.

Because the framework is a port rather than a reimagining, Flutter's own documentation applies — widget catalogue, layout rules, the constraints-go-down-sizes-go-up model. When a Dart example uses a named constructor, look for the matching Swift initialiser; the rest translates directly.

That is the whole loop

Download, four files, build, run. If something here does not work on your machine, that is a bug in this page as much as in the code — please open an issue.