A practical guide for writing, building, debugging, and packaging Vala applications on Windows.
The easiest path is Ooblerg: install a graphical package manager, install native-sdk, vala, gtk4, and any other libraries you need, then build from normal Windows terminals, VS Code, PowerShell, or cmd.exe. You do not need to develop inside an MSYS2 shell unless you specifically want that workflow.
MSYS2 is documented below as an alternative because it is widely used and has a large package set. For new Vala/GTK 4 projects, start with Ooblerg.
- Recommended setup: Ooblerg
- Build a console Vala app
- Build a GTK 4 Vala app
- Use VS Code without a custom shell
- Package with sqgipkg on Ubuntu CI
- Why Ubuntu 24.04 / noble is pinned
- MSYS2 alternative
- Troubleshooting
Ooblerg is a Windows package manager and MinGW-w64 sysroot for native GTK/GObject/Vala/SQGI desktop development.
It provides:
- a graphical package manager for installing and removing packages;
- a local Windows user sysroot under
%LOCALAPPDATA%\Ooblerg\sysroot; - a
mingw64\bindirectory that can be added to the current user'sPATH; - Windows-native build tools, compilers, debuggers, GTK 4, Vala, SQGI, GStreamer, GDAL, SDL, and related libraries.
-
Download and install Ooblerg from https://ooblerg.xyz.
-
Launch Ooblerg Package Manager.
-
Refresh the package index.
-
Install at least:
native-sdk vala gtk4Useful extras:
sqgi libgee libsoup3 gstreamer gdal -
Use the Ooblerg package manager's PATH integration to add the sysroot binary directory to the current user's Windows
PATH.
The important directory is:
%LOCALAPPDATA%\Ooblerg\sysroot\mingw64\bin
Restart any already-open terminals or VS Code windows after changing PATH.
Open PowerShell or cmd.exe and run:
valac --version
gcc --version
meson --version
ninja --version
pkg-config --modversion gtk4If these commands work from a normal Windows terminal, your editor and build scripts can use them too.
MSYS2 is mature and widely used, but it is a full Unix-like development environment for Windows. It ships multiple toolchains and shells, and uses a rolling release model. It works well but it can make VS Code tasks, terminals, debugger configuration, and packaging scripts feel more complicated for new Vala users.
Ooblerg is narrower on purpose. It provides a graphical package manager, installs packages into a single Windows user sysroot, and places that sysroot's mingw64\bin directory on the normal Windows PATH. Tools such as valac, gcc, meson, ninja, and pkg-config are then available from PowerShell, Command Prompt, VS Code, and other standard Windows tooling.
Ooblerg currently provides one toolchain: mingw64, matching the Ubuntu 24.04 cross-compiler baseline used by sqgipkg. Its packages are pinned around Ubuntu 24.04 versions, giving Windows development and Ubuntu CI packaging a stable cross-platform reference point.
Do not put both Ooblerg and MSYS2 mingw64\bin directories on PATH for the same project. Pick one environment per terminal/session so pkg-config, DLL lookup, headers, and import libraries all come from the same sysroot.
Create a project directory:
mkdir vala-hello
cd vala-helloCreate meson.build:
project('vala-hello', ['c', 'vala'], version: '0.1.0')
glib_dep = dependency('glib-2.0')
executable(
'vala-hello',
'main.vala',
dependencies: [glib_dep],
install: true,
)Create main.vala:
public static int main (string[] args) {
print ("Hello from Vala on Windows!\n");
return 0;
}Build and run:
meson setup builddir
meson compile -C builddir
.\builddir\vala-hello.exeInstall gtk4 in Ooblerg first.
Create meson.build:
project('vala-gtk4-demo', ['c', 'vala'], version: '0.1.0')
gtk4_dep = dependency('gtk4')
executable(
'vala-gtk4-demo',
'main.vala',
dependencies: [gtk4_dep],
win_subsystem: 'windows',
install: true,
)Create main.vala:
using Gtk;
public class DemoApp : Gtk.Application {
public DemoApp () {
Object (application_id: "org.example.ValaGtk4Demo");
}
protected override void activate () {
var window = new Gtk.ApplicationWindow (this);
window.title = "Vala GTK 4";
window.default_width = 480;
window.default_height = 320;
var button = new Gtk.Button.with_label ("Click me");
button.clicked.connect (() => {
button.label = "Hello from Vala";
});
window.set_child (button);
window.present ();
}
}
public static int main (string[] args) {
var app = new DemoApp ();
return app.run (args);
}Build and run:
meson setup builddir
meson compile -C builddir
.\builddir\vala-gtk4-demo.exeRemove win_subsystem: 'windows' while debugging if you want a console window for print() output.
With Ooblerg on PATH, VS Code can use its normal PowerShell or cmd.exe terminal.
A minimal .vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "meson setup",
"type": "shell",
"command": "meson setup builddir --wipe",
"problemMatcher": []
},
{
"label": "build",
"type": "shell",
"command": "meson compile -C builddir",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "run",
"type": "shell",
"command": ".\\builddir\\vala-gtk4-demo.exe",
"dependsOn": "build",
"problemMatcher": []
}
]
}Optional debugging with GDB:
- Install
gdbthrough Ooblerg, or installnative-sdk, which includes it. - Install a VS Code GDB debugging extension.
- Point the debugger at the executable in
builddirand usegdbfromPATH.
Example .vscode/launch.json for the Native Debug extension:
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "Debug Vala GTK app",
"target": "${workspaceFolder}\\builddir\\vala-gtk4-demo.exe",
"cwd": "${workspaceFolder}",
"gdbpath": "gdb"
}
]
}Ooblerg and sqgipkg solve different parts of the Vala-on-Windows workflow:
- Ooblerg is the local Windows development environment. Use it to install
valac, GTK 4, Meson, Ninja, GDB, and runtime libraries into a normal Windows sysroot onPATH. - sqgipkg is the release packager that produces Windows installers and Linux AppImages. It runs on Ubuntu 24.04, not Windows, and is intended to be used as a GitHub Actions workflow with an Ubuntu 24.04 runner.
The recommended workflow for Windows developers is:
- Develop and test the app locally with Ooblerg.
- Commit the app source, Meson files, resources, icons, and
sqgipkg.jsonmanifest. - Commit a GitHub Actions workflow under
.github/workflows/release.yml. - Let an
ubuntu-24.04GitHub runner build the release artifacts withsqgipkg. - Download test artifacts from the workflow run, or publish them automatically from version tags.
That keeps the Windows setup focused on development. The Windows machine does not need a packaging stack, a Linux shell, or a local sqgipkg install.
From one Ubuntu runner, sqgipkg can produce release artifacts such as:
- Linux x86_64 AppImage;
- Linux aarch64 AppImage;
- Windows x86_64 NSIS installer.
The app does not need to be an SQGI script app. A Vala, C, C++, GTK, libadwaita, GStreamer, or other native GObject-style application should use a native entry manifest. The manifest tells sqgipkg where the built Linux executable and Windows .exe will be, which target packages to stage, and how to build the native project for each target.
Use these references:
- sqgipkg packaging overview
- Vala and native-entry apps
- Targets, Windows installers, AppImages, and architecture matrices
- GitHub Actions release builds
- Verminal, a GTK 4/Vala app using Meson,
sqgipkg, Linux AppImages, and a Windows NSIS installer - Verminal
sqgipkg.json - Verminal release workflow
A Vala/GTK project usually needs two packaging files. For a one-app repository,
put sqgipkg.json at the project root. In this repository, the packageable app
lives in gtk_app/, so its manifest is gtk_app/sqgipkg.json.
First, add sqgipkg.json. Use the Verminal manifest as the shape to copy and adapt. It should describe:
- app name, app ID, icons, resources, and desktop metadata;
entry.type: "native";- Linux executable paths, usually with per-architecture
entry_linuxpaths; - the Windows executable path through
entry_windows; - Linux target architectures such as
x86_64andaarch64; - Debian/Ubuntu packages to stage into private Linux sysroots;
linux.deb.suite: "noble"when the Linux package sysroot should be pinned to Ubuntu 24.04;- Windows runtime packages such as GTK 4, GStreamer, or other MinGW packages needed by the app;
- Meson/Ninja build steps for each target;
- optional NSIS installer metadata such as installer name, install directory, shortcuts, and execution level.
Second, add .github/workflows/release.yml. Use this repository's workflow or
the Verminal workflow as the copy-and-adapt example. The workflow should:
- run on
ubuntu-24.04; - check out your application;
- check out
supercamel/sqgiso the runner can build and installsqgipkg; - install host build dependencies, cross compilers, MinGW-w64, NSIS, and AppImage tooling;
- build and install SQGI /
sqgipkgon the runner; - run the AppImage targets for each Linux architecture;
- run the Windows NSIS target;
- collect the generated AppImages and Windows setup
.exe; - upload artifacts for workflow runs;
- publish release assets when the pushed ref is a version tag such as
v1.0.0.
The Windows installer produced by this workflow is built from the Ubuntu runner. It does not use the developer's local Ooblerg sysroot. Ooblerg gives contributors a convenient Windows development environment; sqgipkg creates its own target sysroots during CI so release builds are reproducible.
Ooblerg and sqgipkg are both pinned around an Ubuntu 24.04 (noble) package baseline so local Windows development and CI packaging do not drift apart.
In practice, that means:
- Ooblerg provides Windows MinGW-w64 packages built from a stable Ubuntu 24.04-era source/toolchain set.
- GitHub Actions should use runs-on: ubuntu-24.04 for release packaging.
- sqgipkg.json should use "suite": "noble" for Linux sysroot creation.
sqgipkg runs on Ubuntu, not Windows, and CI does not reuse the developer's local Ooblerg install. The shared part is the pinned package baseline: Vala, GLib, GTK, GObject Introspection, Meson/pkg-config discovery, Linux AppImage staging, and Windows NSIS staging all stay aligned.
Use MSYS2 if you want the traditional shell-based workflow or need packages that Ooblerg does not currently provide.
-
Install MSYS2 from https://www.msys2.org/.
-
Open the MINGW64 shell.
-
Update MSYS2:
pacman -Syu
Close and reopen the MINGW64 shell if MSYS2 asks you to, then run the update again.
-
Install Vala and build tools:
pacman -S --needed \ mingw-w64-x86_64-toolchain \ mingw-w64-x86_64-vala \ mingw-w64-x86_64-meson \ mingw-w64-x86_64-ninja \ mingw-w64-x86_64-pkgconf \ mingw-w64-x86_64-gdb
-
For GTK 4 development:
pacman -S --needed mingw-w64-x86_64-gtk4
-
For NSIS installers:
pacman -S --needed mingw-w64-x86_64-nsis
If you use MSYS2, configure VS Code to launch the MSYS2 shell so Meson, pkg-config, DLL lookup, and compiler paths all agree:
{
"terminal.integrated.profiles.windows": {
"MSYS2 MINGW64": {
"path": "C:\\msys64\\usr\\bin\\bash.exe",
"args": ["--login", "-i"],
"env": {
"MSYSTEM": "MINGW64",
"CHERE_INVOKING": "1"
}
}
},
"terminal.integrated.defaultProfile.windows": "MSYS2 MINGW64"
}Add C:\msys64\mingw64\bin to PATH only for MSYS2-based projects, and avoid mixing it with Ooblerg's mingw64\bin in the same session.
vala_hello/is the small console app from the README.gtk_app/is the GTK 4 app from the README and includesgtk_app/sqgipkg.jsonfor release packaging..github/workflows/release.ymlpackagesgtk_app/onubuntu-24.04withsqgipkg, producing Linux AppImages and a Windows NSIS installer..github/workflows/main.ymlbuilds the examples with MSYS2 as an alternative environment check.
The old manual deployment scripts are preserved under
legacy-msys2-manual-deploy/ as historical references. For new projects,
prefer sqgipkg in CI for release packaging because it understands target
sysroots, generated launchers, GTK/GI runtime data, AppImage output, and NSIS
output.
Install vala in Ooblerg, enable the Ooblerg PATH integration, and restart the terminal or VS Code window.
Check:
where valac
valac --versionInstall gtk4 in Ooblerg. Then check that pkg-config is the Ooblerg one:
where pkg-config
pkg-config --modversion gtk4If the path points at MSYS2, Git for Windows, Chocolatey, or another toolchain, clean up PATH for that terminal.
Run it from a terminal that has Ooblerg's mingw64\bin on PATH, or use the CI sqgipkg workflow so the required DLLs and runtime data are staged with the app.
For GUI apps, use:
win_subsystem: 'windows'Remove that while debugging if you need console output.
Add a temporary workflow step before the NSIS step that runs sqgipkg --target win-dir and uploads that directory as an artifact. That makes it easier to
inspect the staged .exe, DLLs, icons, launchers, and runtime data before
debugging installer generation.
Once the staged directory is correct, switch the workflow back to the normal Windows installer target.
sqgipkg names output files from name in gtk_app/sqgipkg.json. If you
change:
"name": "ValaGtk4Demo"
update the artifact copy paths in .github/workflows/release.yml too.