Thanks for your interest in nui-sftp. This document covers how to build the project, where the moving pieces live, the conventions the code follows, and how changes are validated in CI.
nui-sftp is a cross-platform (Linux and Windows) SSH/SFTP workbench. The frontend is written in C++ and compiled to WebAssembly via Emscripten using Nui; the backend is a native C++ application that hosts a WebKit/WebView2 webview and talks to libssh. Frontend and backend communicate through Nui's RPC bridge.
Submodules pin most C++ dependencies. System packages (libssh, boost, fmt, crypto++, webkitgtk on Linux) are pulled from the distro or MSYS2.
- Use the templates under .github/ISSUE_TEMPLATE/ for bug reports, feature requests, and general questions.
- Do not file security issues publicly. The disclosure process is in SECURITY.md.
git clone https://github.com/5cript/nui-sftp.git
cd nui-sftp
./setup.sh # equivalent to: git submodule update --init --recursiveAll twelve submodules under dependencies/ (Nui, roar, gimo, traits, 5cript-nui-components, portable-file-dialogs, webview, yaml-cpp, rapidfuzz, spdlog, efsw, promise-cpp) must be present before configuring.
The project uses C++23. CI builds with Clang and Ninja, and I recommend the same locally. The full package list for each platform lives in the workflow files; treat those as authoritative.
See .github/workflows/archlinux.yml. The minimum packages CI installs are:
cmake ninja clang lld git python nodejs
webkitgtk-6.0 curl crypto++ libssh fmt boost boost-libs
nlohmann-json zlib bzip2 zstd xz
Node.js 24 is required for the frontend build (Nui invokes parcel and
npm).
See .github/workflows/windows.yml. Builds run
inside the clang64 MSYS2 environment with:
mingw-w64-clang-x86_64-{clang,cmake,ninja,boost,crypto++,python,
pugixml,fmt,libssh,cppwinrt,zlib,bzip2,zstd,xz,
imagemagick,librsvg}
Visual Studio's MSVC toolchain is not supported.
All builds are plain CMake + Ninja invocations. I use VS Code tasks to drive them
locally; the editor configuration is not committed
(.vscode/, .clangd, and build/ are all gitignored), so this section
documents the raw commands. A minimal tasks.json / launch.json you can drop
into your own .vscode/ is provided at the end of this document.
cmake -B build/clang_debug -S . -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_STANDARD=23 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DNUI_FETCH_TRAITS=OFF -DNUI_FETCH_ROAR=OFF \
-DNUI_SFTP_ENABLE_TESTING=ON \
-DNUI_BUILD_XML_TOOL=ON \
-DNUI_SFT_ENABLE_LANGUAGE_TOOLING=ON
cmake --build build/clang_debugThe frontend WASM module is built as the nui-sftp-emscripten sub-target and
copied into build/clang_debug/bin/../frontend/ as a post-build step, so the
debug binary at build/clang_debug/bin/nui-sftp finds it at runtime. Both
targets can be rebuilt individually:
cmake --build build/clang_debug --target nui-sftp-emscripten # frontend only
cmake --build build/clang_debug --target nui-sftp # backend onlycmake -B build/clang_release -S . -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_STANDARD=23 \
-DNUI_FETCH_TRAITS=OFF -DNUI_FETCH_ROAR=OFF \
-DCMAKE_INSTALL_PREFIX="$PWD/install"
cmake --build build/clang_release --target nui-sftp
# Installs into build/install:
./scripts/deploy.shThe release configure omits tests and the XML tool by default.
A separate Emscripten module under webpage/ is built with
-DBUILD_WEBPAGE=ON. It produces its own bin/ independent of the main
frontend:
cmake -B build/clang_webpage -S . -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_STANDARD=23 \
-DNUI_FETCH_TRAITS=OFF -DNUI_FETCH_ROAR=OFF \
-DBUILD_WEBPAGE=ON
cmake --build build/clang_webpage --target nui-sftp-webpage-emscriptenwebpage/scripts/serve.sh and webpage/scripts/build_and_deploy.sh handle
local preview and remote deployment.
The Flatpak is not built from this repository. Its manifest and build scripts live in the companion nui-sftp-deploy repo alongside the PKGBUILD, AppImage Dockerfile, and AppStream metainfo. To build a Flatpak locally:
git clone https://github.com/5cript/nui-sftp-deploy.git
cd nui-sftp-deploy
./scripts/build_flatpak.sh # produces build/nui-sftp.flatpak
flatpak install --user --reinstall build/nui-sftp.flatpak
flatpak run org.nuicpp.nui_sftpThe manifest pins OFFLINE_BUILD=ON and OMIT_FRONTEND_BUILD=ON; the frontend
ships as a pre-built artifact (the nui-sftp-frontend tarball from the
matching release) rather than being compiled inside the sandbox.
These are declared at the top of CMakeLists.txt. The defaults are sensible; override them only when needed.
| Option | Default | Purpose |
|---|---|---|
NUI_SFTP_ENABLE_TESTING |
OFF |
Build the GoogleTest suites under */test/ |
NUI_SFT_ENABLE_LANGUAGE_TOOLING |
OFF |
Build the language-check CLI tool |
NUI_BUILD_XML_TOOL |
(via Nui) | Build the xml-to-nui SVG/XML converter |
OMIT_FRONTEND_BUILD |
OFF |
Skip the Emscripten frontend |
OFFLINE_BUILD |
OFF |
Adjust dependency fetching for sandboxed builds (Flatpak, Yocto) |
BUILD_WEBPAGE |
OFF |
Build the marketing webpage module |
FETCH_YAML_CPP / FETCH_RAPIDFUZZ / FETCH_SPDLOG / FETCH_EFSW |
ON |
Fetch vs. use system version |
WEBKIT_PATH |
"" | Use a custom WebKit build instead of system pkg-config |
TAR_ARCHIVE_HAS_XZ |
ON |
Enable liblzma compression in the tar-archive library |
The backend is a normal native binary; debug it directly with gdb or lldb:
gdb --args build/clang_debug/bin/nui-sftpTwo caveats worth knowing about:
-
Tell the debugger to ignore
SIGUSR1andSIGXCPU, otherwise the WebKit and JS runtimes will repeatedly trip it. For gdb:handle SIGUSR1 nostop noprint handle SIGXCPU nostop noprint pass -
On some WebKitGTK driver setups the DMABUF renderer crashes the webview. Export
WEBKIT_DISABLE_DMABUF_RENDERER=1before launching if you see GPU process failures.
Test binaries live under build/clang_debug/test/ and can be launched the
same way.
Test sources live next to the code they cover, under */test/:
ssh/test/ssh
backend/test/backend
utility/test/utility
shared_data/test/shared_data
tar-archive/test/tar_archive
They are enabled only when NUI_SFTP_ENABLE_TESTING=ON. Build and run them
explicitly, or use ctest:
# Build every test target in one go.
cmake --build build/clang_debug --target \
nui-tests nui-sftp-backend-test nui-sftp-utility-test \
nui-sftp-shared-data-test nui-sftp-ssh-test
# Run the whole suite.
ctest --test-dir build/clang_debug --output-on-failure
# Or run an individual binary.
./build/clang_debug/test/nui-sftp-utility-testTests must place transient files under the build tree (typically programDirectory / "temp"),
not /tmp or std::filesystem::temp_directory_path(). This keeps runs isolated
and reproducible across machines.
-
C++ source is formatted by clang-format using .clang-format (LLVM-derived, 120-column, 4-space indent, braces on their own line). Run it manually if your editor does not format on save:
clang-format -i path/to/file.cpp
-
I recommend clangd as the language server. The repository's own per-directory configuration is not committed (
.clangdis gitignored), but generatingcompile_commands.jsonvia-DCMAKE_EXPORT_COMPILE_COMMANDS=ON(already set in the build invocations above) is enough for most setups. A minimal local.clangdthat points at the debug build is:CompileFlags: CompilationDatabase: "build/clang_debug" Add: - "-std=c++23"
Frontend translation units compile through Emscripten and need a separate block matching paths under
frontend/,nui-file-explorer/, anddependencies/Nui/nui/include/nui/frontend/. Point that block atbuild/clang_debug/module_nui-sftpinstead. -
Include order: project and library headers first, then system headers, then the C++ standard library.
-
Naming: spell types out (
Implementation, notImpl;Channel, notCh). Short names are tolerated for hot-path members likeimpl_. Trailing_is reserved forprivate/protectedmembers; public struct fields have no suffix. Strongly-typed IDs fromids/(Ids::ChannelId,Ids::SessionId,Ids::OperationId) replace barestd::stringfor identifiers. -
Doc comments use the
/** @brief ... @param ... */block form, placed above the member they document. Do not write trailing comments and do not align doc comments across adjacent members. -
Default locals to
constwhere they are not reassigned. Suffix unsigned integer literals withu. Avoid thek-prefix convention. -
Keep regular comments terse. Drop banner and "what does this do" comments; comment only when the why is not obvious from the code.
The frontend uses Nui's C++ DSEL for the DOM. A few points worth knowing if you are touching it:
- Empty children must be expressed with
Nui::nil(). A default-constructedNui::ElementRenderer{}is undefined behaviour. - Do not call
eval()from C++. Put support JS understatic/source/, expose it onglobalThis, and call it from C++ viaNui::val::global(...). Observed<T>::modifyNow()syncs the UI immediately. Component setters should not sync; assign theObserved(or call.modify()for full-range invalidation) and let the caller batch.- Themes use a
--brightness-mixinvariable:blackon dark themes,whiteon light. Several rules mix it in at 80%, so flipping it across themes affects surface tones.
The build runs scripts/check_licenses.py as part of the main target. It fails the build if a CMake-declared dependency is missing from licenses/third_party.spdx.json, if any referenced license text file is empty, or if a placeholder slipped through. When adding a new dependency, update the manifest and add its license file before pushing.
A standalone licenses tarball is produced in CI by scripts/build_licenses_bundle.py and attached to tagged releases.
Two workflows under .github/workflows/ run on every PR
into main and on v* tag pushes:
- archlinux.yml builds a Release config
inside an
archlinux:base-develcontainer, runsctest, packages the frontend tarball and licenses tarball, and on tag pushes uploads them to the GitHub release and to MEGA S4. - windows.yml builds the same Release config
under MSYS2
clang64and runsscripts/deploy.shto produce the Windows artifacts.
PRs should pass both workflows. If you add a new CMake option or dependency, update both workflows if it changes the build surface.
- Fork the repository and create a feature branch from
main. - Keep commits focused; prefer small, reviewable changes over large rewrites.
- Run a debug build with tests enabled locally and make sure
ctest --output-on-failurepasses frombuild/clang_debug. - Format any C++ changes with clang-format.
- Open the PR against
main. Describe the user-visible behaviour, the motivation, and anything reviewers should pay particular attention to. - CI will run the Arch and Windows workflows; address any failures before requesting review.
.vscode/ is gitignored. If you want a VS Code setup similar to mine, drop
the snippets below into .vscode/tasks.json and .vscode/launch.json. They
cover the configure/build/test cycle and a single debug launch; extend them as
needed.
{
"version": "2.0.0",
"options": { "cwd": "${workspaceFolder}" },
"tasks": [
{
"label": "configure_debug",
"type": "shell",
"command": "cmake",
"args": [
"-S", ".", "-B", "build/clang_debug", "-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Debug",
"-DCMAKE_C_COMPILER=clang", "-DCMAKE_CXX_COMPILER=clang++",
"-DCMAKE_CXX_STANDARD=23",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
"-DNUI_FETCH_TRAITS=OFF", "-DNUI_FETCH_ROAR=OFF",
"-DNUI_SFTP_ENABLE_TESTING=ON"
],
"problemMatcher": []
},
{
"label": "build_debug",
"type": "shell",
"dependsOn": "configure_debug",
"command": "cmake",
"args": ["--build", "build/clang_debug"],
"group": { "kind": "build", "isDefault": true }
},
{
"label": "ctest",
"type": "shell",
"dependsOn": "build_debug",
"command": "ctest",
"args": ["--test-dir", "build/clang_debug", "--output-on-failure"],
"group": "test"
}
]
}{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "launch",
"name": "Build & Debug",
"target": "${workspaceFolder}/build/clang_debug/bin/nui-sftp",
"cwd": "${workspaceFolder}",
"preLaunchTask": "build_debug",
"debugger_args": [
"--init-eval-command", "handle SIGUSR1 nostop noprint",
"--init-eval-command", "handle SIGXCPU nostop noprint pass"
]
}
]
}(The gdb debug type comes from the
Native Debug
extension. Substitute your preferred extension's schema if you use a different
one.)
By contributing, you agree that your contributions are licensed under the same terms as the rest of the project. See LICENSE.