Everything you need to build Alchemy from source: platform setup, presets, options, tests, packaging, and troubleshooting.
- Prerequisites
- Platform setup — Windows, macOS, Linux
- Clone and bootstrap
- Configure
- Build
- Configuration types
- Build options
- Running tests
- Packaging
- Troubleshooting
Every platform needs a C++ toolchain plus:
- CMake 4.0+
- Git
- Rust — only for the Velopack update client (
-DAL_USE_VELOPACK=ON), whose C API the build compiles fromindra/rust - .NET SDK — only for Velopack installers; on Linux the AppImage also needs
mksquashfs(squashfs-tools) - Python 3 — only for the tests that spawn a Python peer (see Running tests)
Install commands are platform-specific; see below.
Install the following:
- Visual Studio 2026 — select the Desktop development with C++ workload
- CMake 4.0+
- Git for Windows
- Rust — run
rustup-init.exeand accept defaults (Velopack only) - .NET SDK (packaging only)
Sanity-check in a fresh terminal:
cmake --version
git --version
Install Xcode from the App Store, then run xcode-select --install to get the command-line tools.
Install Homebrew, then the build dependencies:
brew install git cmake zip unzip curl pkgconf automake autoconf autoconf-archive \
gettext libtool rustup dotnet
Put Homebrew's rustup on the path and install a stable toolchain (Velopack only). The formula is keg-only and no longer provides rustup-init; add the export to your shell profile too:
export PATH="$(brew --prefix rustup)/bin:$PATH"
rustup default stable
Install system packages for your distro. Media plays through GStreamer; the VLC media plugin is left out on Linux unless configured with -DAL_BUILD_VLC_PLUGIN=ON, which also needs LibVLC's development package (libvlc on Arch, libvlc-dev on Debian and Ubuntu, vlc-devel on Fedora and openSUSE).
Arch
sudo pacman -Syu automake autoconf autoconf-archive base-devel cmake fontconfig git glib2-devel \
gstreamer gst-plugins-base-libs ninja libglvnd libtool libx11 pkgconf python \
wayland dotnet-sdk zip nasm
Debian 12+
sudo apt install \
autoconf autoconf-archive automake bison build-essential cmake curl flex gettext \
libasound2-dev libaudio-dev libdbus-1-dev libdecor-0-dev libdrm-dev \
libegl1-mesa-dev libfribidi-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libibus-1.0-dev libjack-dev \
libpipewire-0.3-dev libpulse-dev libsndio-dev libtext-unidecode-perl \
libthai-dev libtool libudev-dev libunwind-dev liburing-dev libwayland-dev \
libx11-dev libxcursor-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev \
libxkbcommon-dev libxrandr-dev libxss-dev libxtst-dev linux-libc-dev ninja-build \
pkgconf tar tex-common texinfo unzip zip dotnet-sdk-10.0 nasm
Ubuntu 22.04+
sudo apt install \
autoconf autoconf-archive automake bison build-essential cmake curl flex gettext \
libasound2-dev libaudio-dev libdbus-1-dev libdecor-0-dev libdrm-dev \
libegl1-mesa-dev libfribidi-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libibus-1.0-dev libjack-dev \
libpipewire-0.3-dev libpulse-dev libsndio-dev libtext-unidecode-perl \
libthai-dev libtool libudev-dev libunwind-dev liburing-dev libwayland-dev \
libx11-dev libxcursor-dev libxext-dev libxfixes-dev libxft-dev libxi-dev libxinerama-dev \
libxkbcommon-dev libxrandr-dev libxss-dev libxtst-dev linux-libc-dev ninja-build \
pkgconf tar tex-common texinfo unzip zip dotnet-sdk-10.0 nasm
Fedora / RHEL
AlmaLinux 10:
sudo dnf group install "Development Tools"
sudo dnf install cmake fontconfig-devel git glib2-devel gstreamer1-devel \
gstreamer1-plugins-base-devel libX11-devel libglvnd-devel \
ninja-build python3 wayland-devel dotnet-sdk-10.0
You may need to enable EPEL first: sudo dnf install epel-release
Fedora 44+:
sudo dnf install @development-tools @c-development cmake fontconfig-devel git glib-devel \
gstreamer1-devel gstreamer1-plugins-base-devel libX11-devel \
libglvnd-devel ninja-build python3 \
wayland-devel dotnet-sdk-10.0 perl-IPC-Cmd perl-FindBin perl-Time-Piece \
autoconf-archive perl-open libXcursor-devel wayland-protocols-devel dbus-devel \
ibus-devel mesa-libGLU-devel libxkbcommon-devel mesa-libEGL-devel mesa-libGL-devel \
libXtst-devel libXrandr-devel pipewire-devel pulseaudio-libs-devel alsa-lib-devel \
nasm libXScrnSaver-devel
To build with Clang instead of GCC, also install: sudo dnf install clang lld
OpenSUSE Tumbleweed
sudo zypper in -t pattern devel_basis devel_C_C++
sudo zypper install cmake fontconfig-devel git glib2-devel gstreamer-devel \
gstreamer-plugins-base-devel libglvnd-devel libX11-devel ninja Mesa-libGL-devel \
python3 wayland-devel
Alchemy vendors the Dullahan CEF wrapper — used by the in-world web media plugin — as a git submodule under indra/dullahan. It builds from source as part of the tree, so the submodule must be present before you configure. Clone with --recurse-submodules:
git clone --recurse-submodules https://github.com/AlchemyViewer/Alchemy.git alchemy
cd alchemy
dotnet tool restore # Velopack installers only
Already cloned without --recurse-submodules? Fetch the submodules before configuring:
git submodule update --init --recursive
After pulling upstream changes, run the same command to keep the submodule in sync with the revision the tree expects.
Build configuration is driven by CMake presets. indra/CMakePresets.json includes one file per generator under indra/cmake/presets/ (vs2026.json, ninja.json, xcode.json), each of which includes base.json, the hidden bases they are composed from. generate.py beside them writes all five; edit its tables, not the JSON. A preset selects the generator (Visual Studio, Ninja, Xcode), the target architecture, and whether proprietary components are enabled.
List all available presets:
cmake -S indra --list-presets
Preset names follow the pattern <generator>[-<arch>][-os]:
-ossuffix — open-source only. Excludes proprietary components (KDU JPEG2000 codec, FMOD audio, and other non-free libraries).- No
-ossuffix — setsAL_ENABLE_PROPRIETARY=ON. Requires licensed source for the proprietary components and is only useful if you have access to them.
Most contributors want the -os variants.
<generator>[-os]-fullopt (with -arm64 / -x64 on macOS and Windows) is that preset with the optimizations of a shipped build: LTO on, Tracy and Release-configuration debug logging off. The channel is not part of it — pass -DAL_CHANNEL=... as for any preset — and neither is the Velopack update client (-DAL_USE_VELOPACK=ON), which CI adds. The Ninja ones default to the Release configuration. The hidden fullopt preset carries the three settings for a preset of your own, for example {"name": "mine", "inherits": ["ninja-os", "fullopt", "mold"]} in CMakeUserPresets.json.
| Preset | Platform | Generator |
|---|---|---|
vs2026-os |
Windows | Visual Studio |
vs2026-os-arm64, vs2026-os-x64 |
Windows | Visual Studio |
ninja-os |
Linux | Ninja Multi-Config |
ninja-os-lld, ninja-os-mold |
Linux | Ninja Multi-Config |
ninja-os-clang, ninja-os-clang-lld |
Linux | Ninja Multi-Config |
ninja-os-arm64, ninja-os-x64 |
macOS | Ninja Multi-Config |
xcode-os, xcode-os-arm64, xcode-os-x64 |
macOS | Xcode |
Configure with:
cmake -S indra --preset <preset-name>
This creates a build tree at build-<HostSystem>-<preset>/ next to the source — e.g. build-Windows-vs2026-os/, build-Linux-ninja-os/, build-Darwin-xcode-os-arm64/.
The first configure run downloads and builds every vcpkg dependency from source. Expect 30–60+ minutes and several GB of disk; subsequent configures finish in seconds.
An optional R2 binary cache can restore matching dependencies. The setup guide covers pipeline environment variables, read-only developer access, retention, and rollout checks.
- macOS —
xcode-osandninja-os(no arch suffix) pick the host architecture. Use the explicit-arm64/-x64preset to cross-build (e.g. an arm64 bundle from an Intel Mac). - Windows architecture —
vs2026-os(no arch suffix) builds for Visual Studio's default, the host.vs2026[-os][-fullopt]-arm64andvs2026[-os][-fullopt]-x64name the architecture, natively or cross from the other host; CI builds and tests arm64 natively on an arm64 runner. Cross-compiling for arm64 needs the Visual Studio component MSVC ARM64 build tools. Ninja builds for the architecture of the Developer Command Prompt it runs in (VsDevCmd -arch=arm64). The triplet follows the architecture the compiler targets,arm64-windows-alchemy[-release]with no ISA tier, and a configure whose compiler and triplet disagree stops with an error. NVAPI is x64-only and is left out of arm64 builds, as are the DirectX shader compiler DLLs, which CEF ships for x64 only. Thevlc-binport supports only x64 on Windows so far, so an arm64 configure stops at the vcpkg install until it gains an arm64 build. - Linux linker —
ninja[-os][-fullopt]-lldandninja[-os][-fullopt]-moldlink with lld or mold instead of the toolchain's default, throughCMAKE_LINKER_TYPE. LTO through lld needs Clang, because lld cannot read GCC's LTO objects; mold reads both. - Linux with Clang —
ninja[-os][-fullopt]-clang, with-lldor-moldafter it to pick the linker too. GCC and Clang builds of some ports are not interchangeable, so a viewer built with Clang takes the-clangtriplets, whose ports Clang builds as well, throughcmake/toolchains/linux-clang.cmake; the first configure builds that dependency tree from scratch. The triplet follows the compiler however it was chosen — a preset,-DCMAKE_CXX_COMPILER=clang++,CXX=clang++, or ac++that is Clang — and a configure whose compiler and triplet disagree stops with an error. The ports are built with theclangandclang++onPATH. The hiddenclang,lldandmoldpresets set the compiler andCMAKE_LINKER_TYPE, andccacheandsccacheset the compiler launcher, for a preset of your own inCMakeUserPresets.json, for example{"name": "mine", "inherits": ["ninja-os", "clang", "mold", "ccache"]}. A compiler cache needs/Z7-style debug info on MSVC, which this tree does not use, so the launcher presets are for Linux and macOS. - vcpkg triplet — chosen from the generator, the architecture,
AL_ISA_TIERand, on Linux, the compiler:<arch>-<os>-alchemy[-clang][-avx2|-avx512][-release], where-releasemeans a single-configuration tree that is not Debug and skips the debug ports. Pass-DVCPKG_TARGET_TRIPLET=<name>to choose one yourself; CI does, to take release-only ports under a multi-config generator.
Workflow presets run configure and build as a single command. Useful for CI and one-off release builds:
cmake --workflow --preset ninja-os-release
cmake --workflow --preset vs2026-os-release
cmake --workflow --preset xcode-os-release
cmake --workflow --preset vs2026-os-fullopt-release
See workflowPresets in the generator files under indra/cmake/presets/ for the full set.
After configuring, build with CMake or your IDE.
# Multi-config generators (VS, Xcode, Ninja Multi-Config)
cmake --build <build-dir> --config Release
# Or use a build preset
cmake --build --preset ninja-os-release
# Visual Studio
start .\build-Windows-vs2026-os\Alchemy.slnx
# Xcode
open ./build-Darwin-xcode-os-arm64/Alchemy.xcodeproj
.slnxis the newer Visual Studio solution format. Requires VS 2026.
The viewer executable lands under build-<OS>-<preset>/newview/<Config>/:
| Platform | Path |
|---|---|
| Windows | build-Windows-<preset>\newview\<Config>\<ChannelName>.exe |
| macOS | build-Darwin-<preset>/newview/<Config>/<ChannelName>.app |
| Linux | build-Linux-<preset>/newview/<Config>/<ChannelName> |
<ChannelName> follows AL_CHANNEL (default Alchemy Test → AlchemyTest.exe / AlchemyTest.app).
Ninja and Xcode presets are multi-config; Visual Studio presets always are. Every configure preset has a build preset per configuration, named <preset>-<config> in lower case: ninja-os-debug, ninja-os-optdebug, ninja-os-relwithdebinfo, ninja-os-release, and likewise for the others. --config <Config> on the command line overrides the preset's configuration.
| Configuration | Libraries | Asserts | Notes |
|---|---|---|---|
Debug |
debug | yes | Slowest; full debugging of viewer and deps |
OptDebug |
release | yes | Optimized libs with debuggable viewer code |
RelWithDebInfo |
release | yes | Default for Ninja presets; ship-adjacent with asserts |
Release |
release | no | Ship builds |
Override any option at configure time with -D<NAME>=<VALUE>. For example:
cmake -S indra --preset ninja-os -DAL_BUILD_TESTS=ON -DAL_USE_OPENAL=ON
Options are defined in indra/CMakeLists.txt. The most commonly used:
| Option | Default | Description |
|---|---|---|
AL_BUILD_VIEWER |
ON | Build the viewer executable |
AL_BUILD_APPEARANCE_UTILITY |
OFF | Build the appearance utility |
AL_BUILD_TESTS |
OFF | Build and run unit + integration tests |
AL_ENABLE_GL_TESTS |
ON | Run the tests that render on a hidden window; off, they are built and registered disabled (needs AL_BUILD_TESTS) |
AL_BUILD_DOCS |
OFF | Add the doc target (API documentation with Doxygen) |
AL_VCPKG_INSTALL |
ON | Let configure run vcpkg install when the manifest, the registry configuration, the triplets or the feature list changed; off leaves the ports to you |
AL_BUILD_PACKAGE |
ON | Add the package target: the CPack archive of the installed tree (zip, tar.xz, dmg), and on Linux the .deb and .rpm |
AL_USE_VELOPACK |
OFF | Add the velopack target, and the Velopack update client to the viewer |
AL_SOURCEID |
$sourceid |
Referring agency recorded in settings_install.xml |
| Option | Default | Description |
|---|---|---|
AL_USE_FAUDIO |
ON | FAudio audio engine |
AL_USE_OPENAL |
OFF | OpenAL audio engine |
AL_USE_FMODSTUDIO |
ON | FMOD Studio audio engine, which takes precedence over the others (needs AL_ENABLE_PROPRIETARY, so off in -os builds, and access to the private registry) |
| Option | Default | Description |
|---|---|---|
AL_ENABLE_PROPRIETARY |
OFF | Allow the non-free libraries below |
AL_USE_KDU |
OFF | Kakadu JPEG2000 codec (needs AL_ENABLE_PROPRIETARY) |
AL_USE_DISCORD |
ON | Discord rich presence through the Social SDK (needs AL_ENABLE_PROPRIETARY and access to the private registry; held off on Linux arm64, which the SDK is not built for) |
Some proprietary ports come from AlchemyViewer's private vcpkg registry,
https://github.com/AlchemyViewer/private-registry, which
indra/vcpkg-configuration.json lists beside the public one. vcpkg fetches it
with plain git, and only when an option above asks for one of its ports, so an
open-source build never touches it. To use it, git ls-remote on that URL
must succeed without a prompt, through Git Credential Manager or
gh auth setup-git. With SSH keys only, send the organisation's HTTPS URLs
over SSH, from inside the viewer checkout so the rewrite stays with it:
git config --local url."git@github.com:AlchemyViewer/".insteadOf "https://github.com/AlchemyViewer/"
With --global instead, it applies to every AlchemyViewer clone on the
machine.
The registry's README covers CI and adding ports.
| Option | Default | Description |
|---|---|---|
AL_USE_TRACY |
ON for test builds | Tracy profiler support |
AL_ENABLE_TRACY_ON_DEMAND |
ON | Only profile when a Tracy server connects |
AL_ENABLE_TRACY_LOCAL_ONLY |
ON | Disallow remote Tracy profiling |
AL_ENABLE_TRACY_GPU |
OFF | Tracy GPU profiling |
| Option | Default | Description |
|---|---|---|
AL_USE_LTO |
OFF | Link Time Optimization |
AL_ISA_TIER |
v3 |
x86-64 level for the viewer and its vcpkg ports: baseline, v2 (SSE4.2), v3 (AVX2), v4 (AVX-512). Ignored on macOS |
AL_SANITIZERS |
empty | Any of address, undefined, thread (GCC and Clang only) |
AL_ENABLE_WARNINGS_AS_ERRORS |
ON | Treat compiler warnings as errors |
AL_ENABLE_RELEASE_DEBUG_LOGGING |
Test channel only | Keep debug-level logging in Release builds |
AL_USE_WEBRTC |
ON | WebRTC voice (off automatically in sanitized builds) |
| Option | Default | Description |
|---|---|---|
AL_BUILD_CEF_PLUGIN |
ON | Chromium Embedded Framework (in-world web) |
AL_BUILD_VLC_PLUGIN |
OFF on Linux | VLC media plugin; on Linux GStreamer plays media, and VLC only with MediaPluginForceVLC |
AL_BUILD_GSTREAMER_PLUGIN |
ON on Linux | GStreamer media plugin (Linux only): video, audio and the parcel stream |
AL_BUILD_EXAMPLE_PLUGIN |
ON | Reference/example plugin |
| Option | Default | Description |
|---|---|---|
AL_USE_OPENXR |
OFF | OpenXR VR support (experimental) |
AL_USE_SDL_WINDOW |
ON on Linux | SDL-based window management (Linux only; GL through EGL on Wayland and X11 alike) |
AL_SIGNING_IDENTITY |
empty | macOS Developer ID the bundle is signed with; empty signs ad-hoc |
AL_NOTARY_PROFILE |
empty | macOS notarytool keychain profile the velopack target notarizes with; empty skips notarization |
| Option | Default | Description |
|---|---|---|
AL_USE_SENTRY |
OFF | Sentry crash reporting |
AL_ENABLE_CRASH_REPORTING |
OFF | Send crash reports from this build |
Every option the project defines carries the AL_ prefix. Booleans use one of
three verbs: AL_BUILD_<x> produces a target or artifact, AL_USE_<x> pulls
in a dependency or picks a backend, AL_ENABLE_<x> switches a behaviour.
Values are AL_<NOUN>. Configuring with a name from before this scheme
prints a warning naming the replacement.
See indra/CMakeLists.txt for the complete list.
The CMake files are formatted with gersemi (pip install gersemi); the configuration is .gersemirc at the repository root, and it reads the project's own command definitions from indra/cmake so al_add_test and friends format like the built-ins. Format what you touched before committing:
gersemi -i indra/CMakeLists.txt indra/cmake/*.cmake indra/*/CMakeLists.txt
gersemi --check on the same paths reports what would change without changing it.
Enable tests at configure time:
cmake -S indra --preset <preset> -DAL_BUILD_TESTS=ON
Four tests drive a Python peer (llleap, llprocess, llsdserialize, llcorehttp); they need a Python 3 interpreter with the llsd package (pip install -r requirements.txt, in a venv if you like) and are registered disabled when configure finds none.
LL's LSL compiler (indra/lscript), restored as a reference for the script tests, is built only with tests on. Its lexer, grammar, newer event nodes and library table are written at build time by lsl-definitions' own generator, which needs Python with PyYAML and llsd (both in requirements.txt), and then put through bison and flex. Where any of them is missing, configure leaves the library out and the configuration report says what it needs. Nothing else in the build runs Python.
The llrender suites render on a hidden SDL window with the platform's own GL -- WGL on Windows, EGL on Linux, and where Linux has no display SDL's offscreen driver over Mesa (set LIBGL_ALWAYS_SOFTWARE=1 for llvmpipe on a machine with no GPU). A host with no GL 4.1 to give, such as a CI runner without a graphics driver, configures with -DAL_ENABLE_GL_TESTS=OFF: those suites still build, and CTest reports them as not run rather than failed. They carry the label gl, so ctest -LE gl skips them for one run.
Build, then run with CTest:
cmake --build <build-dir> --config RelWithDebInfo
ctest --test-dir <build-dir> --output-on-failure
Unit tests live alongside the library they cover in indra/<library>/tests/, written against the TUT (Template Unit Test) framework. Integration tests are in indra/integration_tests/.
indra/newview/skins/xui.xsd is the widget vocabulary: every registered tag, the attributes its parameter block answers to, the parameter elements it takes and the tags valid below it. Point an XML editor at it and a XUI file gets completion and a warning on a name no widget has.
The file is written out of the viewer's own registries, since the viewer is the only place all of them exist: run a developer build, open XUI Studio (Advanced > XUI / Colors > XUI Studio) and press Schema. llui_libtest --schema writes the same thing for the widgets llui registers, which is the part a test in that library can check.
VS Code, with the Red Hat XML extension:
"xml.fileAssociations": [
{ "pattern": "**/skins/**/xui/**/*.xml", "systemId": "indra/newview/skins/xui.xsd" }
]It is regenerated rather than edited, and it is permissive where XUI is ambiguous. A parameter may be written as an attribute or as a nested element, and a colour, image, font or setting name is a string whose vocabulary lives in another file. Those are for the tool's lint to check, not a schema.
A few files under xui/ are data rather than widget trees — strings.xml, mime_types.xml, the llsd files, the contents tables. The schema has no root for those and an editor will say so on their first line; the association is by path and cannot tell them apart.
The install rules in indra/cmake/ViewerInstall.cmake are the package manifest. After every link of the viewer they stage the tree it runs from into the build directory (newview/<Config>/, or newview/<Config>/<Channel>.app on macOS). The same rules write a clean tree anywhere:
cmake --install build-<OS>-<preset> --config Release --prefix <dir>
The archive of that tree comes from CPack — a .zip on Windows, a .tar.xz on Linux, a .dmg on macOS — into the build directory, named Alchemy[_<channel>]_<version>_<arch>:
cpack --config build-<OS>-<preset>/CPackConfig.cmake -C Release
(or the package target). Release archives on Linux and macOS are stripped of debug information on the way. Every package is written with its SHA-256 beside it (<package>.sha256, in sha256sum form). -DAL_BUILD_PACKAGE=OFF leaves CPack out; the install rules stay.
The source package is the committed tree of the repository and its submodules at the checked-out commit — what git ls-files --recurse-submodules names, nothing the build wrote into the source tree — as Alchemy_<version>_src.tar.xz, every entry stamped with the commit's time:
cpack --config build-<OS>-<preset>/CPackSourceConfig.cmake
(or the package_source target under Ninja). Uncommitted changes are not in it, and cpack says so.
The Windows installer, the Linux AppImage and the update packages for every platform come from Velopack: configure with -DAL_USE_VELOPACK=ON, run dotnet tool restore once so the vpk tool is available, and build the velopack target. It installs into newview/velopack/<Config>/app (on Linux, into the usr/bin of newview/velopack/<Config>/<App>.AppDir) and writes the update feed, and on Windows the installer and on Linux the AppImage, to newview/velopack/<Config>/Releases. Each platform and architecture has its own Velopack channel, named for its runtime, since an installed viewer updates from its channel's feed: win-x64, win-arm64, osx-arm64, osx-x64, linux-x64 and linux-arm64. The channel also names the feed, releases.<channel>.json, and the files vpk writes. On macOS vpk adds its updater to the bundle and seals it again, with AL_SIGNING_IDENTITY or ad-hoc, and notarizes it when AL_NOTARY_PROFILE names a profile stored with xcrun notarytool store-credentials.
On Linux the AppImage holds the installed tree as it is, at the AppDir's usr/bin, where the update client looks for the updater and manifest vpk adds; cmake/ViewerAppDir.cmake lays out the rest around it, the AppRun that runs the launcher and the tree's desktop entry and icons. Run from the AppImage, the launcher points the desktop entry at the AppImage file rather than at its mount, and the update client replaces that file in place. A tree from the archive or a system package has no Velopack installation, finds no update manager, and is updated by its package manager. The hosted build's package-linux job makes the AppImage and feed from the build's archive.
The third-party attribution is generated, not kept by hand: cmake/Attribution.cmake reads every installed port's vcpkg.spdx.json and copyright and writes app_settings/packages-info.txt (what the About floater's Licences tab shows) and licenses.txt (every licence text). What vcpkg cannot know — the pieces under indra/externals/, the SDKs from outside vcpkg, and a holder or licence a port's files do not state — is in cmake/attribution.json, as is the list of installed ports that ship nothing and are skipped: build tools, empty ports that stand for a system library, and what is built only for those. A newly added port whose vcpkg.json declares no license stops the build with its name; fix the port, add an override to the table, or, if the viewer ships none of it, skip it with the reason (and the platform, when the port is empty only on some).
On macOS the install step signs the bundle inside out — ad-hoc, or with -DAL_SIGNING_IDENTITY=<Developer ID> — so the CEF helpers keep their sandbox entitlements, and the package step seals it again after stripping the executable. The disk image is APFS: HFS+ decomposes file names, which breaks the seal over the font stand-ins with Japanese names. On Linux the binaries carry an $ORIGIN-relative RPATH and find the data one directory above the executable, so the tree runs from wherever it is unpacked.
On Linux CPack also writes a .deb where dpkg-shlibdeps is installed and an .rpm where rpmbuild is (-G DEB or -G RPM asks for one alone). Each channel is a package of its own — alchemy-viewer for the release channel, alchemy-beta, alchemy-test and so on otherwise — so channels install side by side. A package holds the tree as it is, under /opt/<package>, with the launcher linked as /usr/bin/<package> and the desktop entry, AppStream data and icons in /usr/share, taken from the tree's share/. Both are named for the channel's application ID (org.alchemyviewer.viewer, org.alchemyviewer.viewer.beta, …), which is also the Wayland app ID and X11 class of the viewer's window. CEF's sandbox helper is installed setuid root, which sandboxes the web browser's renderers. Dependencies come from what the binaries link, less the libraries the tree carries; what the viewer loads at run time is listed in cmake/ViewerPackageLinux.cmake. AL_PACKAGE_CONTACT names the maintainer.
A tree from the archive runs where it is unpacked: its launcher, alchemy, adds it to the application menu, or points the channel's entry at it when the entry is another tree's, unless the system already has an entry for the channel or AL_NO_DESKTOP_INTEGRATION is set. It makes the tree the handler of secondlife:// and x-grid-location-info:// links only where no other viewer has them: when nothing handles them, the channel already does, or the entry that did is gone. It asks again on each run, so the links of a viewer removed since come to it. install.sh copies it to /opt/<package> as root or ~/.local/share/<package> otherwise, and install.sh --uninstall takes it away. etc/desktop_integration.sh install|uninstall adds or removes the desktop entry alone.
The hosted build (.github/workflows/build.yaml) writes the Linux archive, .deb and .rpm in the build job, and packages Windows and macOS in jobs of their own, after the build, from the build's install tree or stripped bundle and its newview/package.env. Pull requests are packaged unsigned; other builds are signed when the repository has the secrets, and without them are packaged unsigned with a notice:
| Secret | What |
|---|---|
AZURE_KEY_VAULT_URI, AZURE_KEY_VAULT_CERTIFICATE |
The Azure Key Vault and the name of the Windows code-signing certificate in it |
AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID |
The Entra application AzureSignTool signs as |
MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD |
The Developer ID Application certificate and key, as a base64 .p12, and its password |
MACOS_NOTARY_KEY, MACOS_NOTARY_KEY_ID, MACOS_NOTARY_ISSUER_ID |
An App Store Connect API key (the .p8's text) and its key ID, to notarize with, and the issuer ID of a team key; an individual key has none |
scripts/signing/macos_signing_secrets.py makes the macOS secrets from the exported .p12 and the API key's .p8, checking each as the build will use it, and stores them with gh secret set (--repo) or writes them to files (--output); its header says where each comes from.
vpk signs every Windows binary, its Setup.exe and Update.exe through AzureSignTool. On macOS the job signs the bundle inside out with ViewerCodeSign.cmake, vpk adds its updater, seals, notarizes and staples the bundle, and the job builds the disk image from that bundle with dmgbuild (indra/newview/installers/darwin/dmg_settings.py), then signs, notarizes and staples the image.
The Dullahan CEF wrapper is a git submodule. If you cloned without --recurse-submodules, indra/dullahan is empty and CMake configure stops with an error like:
CMake Error at CMakeLists.txt (add_subdirectory):
The source directory .../indra/dullahan does not contain a CMakeLists.txt file.
Fetch the submodule, then re-run configure:
git submodule update --init --recursive
Expected on the first run: vcpkg downloads and builds every C/C++ dependency from source. Budget 30–60+ minutes and several GB of disk. Subsequent configures reuse the vcpkg cache and finish in seconds.
If the run produces no output for a very long time it usually isn't hung — check CPU and disk activity before killing it.
Alchemy requires CMake 4.0+. If your distro ships something older, install a newer version via pip:
pip install --upgrade cmake ninja
Velopack needs the vpk .NET tool. Install it once per clone:
dotnet tool restore
With AL_USE_VELOPACK=ON the build compiles Velopack's C API, indra/rust/velopack_libc, with cargo through Corrosion, which looks for rustc on the path and in ~/.cargo/bin. Install a stable toolchain:
rustup default stable
On macOS, Homebrew's rustup keeps rustc and cargo in $(brew --prefix rustup)/bin, which must be on the path. A target the toolchain lacks, such as x86_64-apple-darwin for an x86_64 build on Apple silicon, is added with rustup while configuring; -DRust_RUSTUP_INSTALL_MISSING_TARGET=OFF stops that.
By default, warnings are treated as errors. New compiler releases sometimes introduce diagnostics the tree hasn't yet cleaned up. Disable fatal warnings at configure time:
cmake -S indra --preset vs2026-os -DAL_ENABLE_WARNINGS_AS_ERRORS=OFF
.slnx is the newer Visual Studio solution format. Use Visual Studio 2022 17.10+ or Visual Studio 2026, or configure with the vs2022-os preset on an older compatible edition.
You probably configured with a proprietary preset (e.g. ninja, without the -os suffix). Build presets are tied to configure presets — use the matching build preset for whichever configure preset you used (for example ninja-release for ninja).
Double-check the package list for your distro under Platform setup → Linux. Common offenders when a package lookup produces an error like <something>.h not found:
autoconf-archive— required by several vcpkg portslibxkbcommon-dev,libwayland-dev,wayland-protocols— required for SDL window and Wayland supportlibgstreamer-plugins-base1.0-dev— required for the GStreamer media plugin
- Ask on the Discord.
- File a build bug at https://github.com/AlchemyViewer/Alchemy/issues.