The build scripts for the libsmb2 inside dart_smb2. They compile a pinned upstream libsmb2 commit with a small set of patches the package relies on, for macOS, iOS, Android, Windows and Linux.
Everything runs from ./build, a terminal menu where you pick the targets and follow the builds. This repository is also the corresponding source of the LGPL-2.1 binaries dart_smb2 ships (see Licensing).
git clone https://github.com/ales-drnz/libsmb2-scripts
cd libsmb2-scripts
./buildOn Windows use build.cmd. You need Go, plus Docker for Linux and Windows, the Android NDK for Android, and Xcode for Apple targets.
| Platform | Architectures | File in release_builds/ |
|---|---|---|
| macOS | arm64 and x86_64 (universal) | libsmb2_macos.xcframework.zip |
| iOS | arm64 device, arm64 and x86_64 simulator | libsmb2_ios.xcframework.zip |
| Android | arm64-v8a, armeabi-v7a, x86_64 | libsmb2_android-<abi>.so |
| Linux | x86_64, aarch64 | libsmb2_linux-<arch>.so |
| Windows | x86_64, arm64 | libsmb2_windows-<arch>.dll |
Each library exports libsmb2's public API (lib/libsmb2.syms) and nothing else, and has no SONAME, so the app bundles it under its own name. The Linux ones reference no glibc symbol newer than 2.25 and are built and tested on glibc 2.31, the oldest supported; the Android ones depend on libc only and align their 64-bit segments to 16 KB; the Windows ones import only system DLLs; all of them are stripped: the Linux and Android ones keep only the dynamic symbol table, the Windows ones carry no DWARF and no COFF symbol table beside their export table, and the Apple ones have no local symbols; the Apple ones target iOS 13.0 and macOS 10.15.
The libsmb2 commit and the SHA-256 of its tarball are set in scripts/shared/_versions.sh, the Docker base images are pinned by digest, llvm-mingw by SHA-256 and the apt packages of the Linux and Verify images by version, and the Android NDK is pinned by version. Linux, Android and Windows builds give the same bytes on any machine. ./build manifest writes release_builds/manifest.json and SHA256SUMS once all nine libraries are there (CI always builds all nine), with the release, the libsmb2 pin, the source commit, the patches, the verified symbols and the hash of every library, and copies libsmb2's licence texts (COPYING, LICENCE-LGPL-2.1.txt) beside them; the xcframework zips carry them too.
| Tool | Needed for |
|---|---|
| Go | always, it runs ./build |
| Docker | Linux, Windows and Verify |
| Android NDK 28.2.13676358 (r28c) | Android, from the Android Studio SDK Manager |
| Xcode | macOS and iOS |
| Host | Targets |
|---|---|
| macOS | all of them |
| Linux | all except macOS and iOS (Android with the NDK) |
| Windows | Linux and Windows |
Apple targets need a Mac. GitHub Actions builds everything, whatever you have.
Building needs only this repo. The libraries, manifest.json, SHA256SUMS and libsmb2's licence texts land in release_builds/; the pinned tarball is cached in sources/cache/; ./build sources writes the patched tree that dart_smb2's tool/ffigen.dart reads to sources/libsmb2-src/.
Run ./build. Arrow keys move, Space selects, Enter confirms, Q quits. The keys for each screen are listed at the bottom.
Three tabs:
- Compile: pick platforms and architectures, then press Build. Each build shows its progress and time, and Enter opens its log. The queue stops at the first failure.
- Tools: write
manifest.jsonandSHA256SUMSfor the built libraries, with the licence texts (Manifest), check them (Verify), and export the patched sources fordart_smb2's bindings (Sources). - Docker: see, build or delete the Docker images. The image a build needs is created automatically the first time, and again when
docker/Dockerfilechanges.
The patch set applied to upstream libsmb2, read from patches/libsmb2/: one row per patch, with its scope (shared or windows) and the reason it exists, from the script's docstring.
On a Mac with Xcode:
./build macos # universal xcframework
./build ios # device and simulator xcframeworkBoth build in Docker and run as your user, so release_builds/ stays yours. Linux builds on Ubuntu 20.04, for the glibc 2.31 floor, and cross-compiles aarch64; Windows uses llvm-mingw for both architectures:
./build linux # x86_64 and aarch64
./build windows # x86_64 and arm64Android builds on the host with the pinned NDK, found in the SDK folder or through NDK_HOME (or ANDROID_NDK_HOME, ANDROID_NDK). Another NDK version stops the build unless ALLOW_ANY_NDK=1. Without the NDK the Android targets are greyed out, ./build all skips them, and naming one fails.
./build android # all three ABIs
./build android-arm64-v8a # one ABI./build accepts targets as arguments and runs them in order, stopping at the first failure:
./build all # everything this host can build; the rest is skipped
./build linux verify # build Linux, then verify everything built
./build list # every target./build all skips what this host cannot build with a line saying why (macOS and iOS off a Mac, Android without the NDK). It writes the manifest only when it skipped nothing, so a Linux machine never produces a manifest.json for part of a release; write_manifest.sh itself refuses anything but the nine libraries, and ALLOW_PARTIAL=1 ./build manifest writes one for a subset, with a warning. A target named explicitly that cannot run here is an error, and so is a group such as android none of whose targets can.
These variables are passed to the builds:
| Variable | Effect |
|---|---|
JOBS=N |
parallel compile jobs, all cores by default |
FORCE_DOWNLOAD=1 |
download the libsmb2 tarball again |
KEEP_BUILD=1 |
keep the build folders |
Every push to a release/ branch, and every pull request, runs .github/workflows/build.yml: it builds all nine libraries on GitHub's runners, Apple included (macos-26, with Xcode 26.6 pinned through DEVELOPER_DIR), collects them with manifest.json, SHA256SUMS and the licence texts in a libsmb2-release artifact, and checks them as in 4.3 on an x86_64 and an arm64 runner, so both Linux libraries are also loaded on glibc 2.31.
From 0.2.0, dart_smb2 gets libsmb2 through a build hook (hook/build.dart). When an app builds, the hook downloads the library for the target from the package's libsmb2-rN GitHub release and checks its SHA-256. To point the package at a release, run this in dart_smb2:
tool/update_libsmb2.sh r9 # a published release
tool/update_libsmb2.sh r9 path/to/manifest.json # before publishing itIt writes the release and the hashes from manifest.json into the hook.
To try a local build in an app, point the hook at release_builds/ in the app's pubspec.yaml. The files are bundled as they are, without a checksum:
hooks:
user_defines:
dart_smb2:
libsmb2: path/to/libsmb2-scripts/release_builds./build verify checks every library in release_builds/ in the verify Docker image: that all nine are there and export every function in verify/symbols.txt (the ones dart_smb2 binds), that libsmb2's licence texts are there and in both zips, that SHA256SUMS matches, the ELF or PE machine, that every library is stripped (no .symtab on ELF, no .debug_* section or COFF symbol on PE, no local symbol in a Mach-O slice), the glibc 2.31 floor, the 16 KB alignment and libc-only dependencies on Android, the system-only imports on Windows, the slices and minimum OS on Apple, and the macOS framework symlinks. The library for the host's architecture is also loaded, its symbols resolved, and a context created and destroyed.
- Build all nine libraries, usually by pushing a
release/branch (see 3.5). - In
dart_smb2, runtool/update_libsmb2.sh rN path/to/manifest.jsonwith the manifest of that Build, and push its ownrelease/branch. Whilelibsmb2-rNis not published, its CI finds the latest successful Build of rN by itself and tests its libraries (it needs the secretLIBSMB2_SCRIPTS_TOKEN; seedart_smb2's.github/workflows/ci.yml). - Tag the commit
libsmb2-rNin this repository. - Create the
libsmb2-rNrelease ondart_smb2and upload the libraries,COPYING,LICENCE-LGPL-2.1.txt,manifest.jsonandSHA256SUMS, naming this repository's commit in its notes. From then ondart_smb2's CI takes the published release.
./build is a small Go program (Bubble Tea) that runs the shell scripts in scripts/, one per platform. Apple targets build with Xcode and Android with the NDK on the host; Linux, Windows and Verify run in a Docker image from docker/Dockerfile, which has one stage each.
Each build downloads the pinned tarball and checks its SHA-256, extracts a fresh tree, installs the static config.h (scripts/shared/config/), applies the patches, compiles lib/*.c (all of libsmb2 except the Kerberos wrapper) and links one library that exports lib/libsmb2.syms. The link order is fixed (LC_ALL=C), so the same sources give the same bytes.
Every patch is a Python script with a docstring that says why it exists. It finds its place by exact text and stops the build when upstream has changed underneath it. The build applies them in alphabetical order, but none depends on that: any order gives the same tree.
| Patch | Scope | What it does |
|---|---|---|
patch_compound_callbacks.py |
shared | calls every callback of a compound that times out before it is sent, so rmdir, unlink and the other path calls report the timeout |
patch_create_data_leaks.py |
shared | frees the call data of unlink, mkdir and ftruncate when their first request cannot be built |
patch_nterror_reporting.py |
shared | records the NT status on open and timeout failures |
patch_poll_eintr.py |
shared | retries the sync API's poll() on EINTR |
patch_rmdir_disposition.py |
shared | makes rmdir fail on a directory that is not empty |
patch_truncate_non_directory.py |
shared | makes truncate fail on a directory |
patch_unlink_non_directory.py |
shared | makes unlink fail on a directory, and so on a link to one (see below) |
patch_utimes.py |
shared | adds smb2_utimes(), behind dart_smb2's setFileTimes |
patch_windows_csprng.py |
windows | uses BCryptGenRandom for the client challenge, salt and nonce |
patch_windows_headers.py |
windows | builds with llvm-mingw's headers |
patch_windows_sockets.py |
windows | keeps 64-bit socket handles whole |
Links to directories: unlink opens the link itself (FILE_OPEN_REPARSE_POINT) and now asks for a non-directory, so on Windows a directory junction or directory symlink is no longer removed by unlink, which fails with STATUS_FILE_IS_A_DIRECTORY: remove it with rmdir, which removes the link and leaves its target alone. Samba follows symlinks on the server and libsmb2 does not negotiate the SMB3 POSIX extensions, so a Samba symlink to a directory cannot be removed over SMB2 at all (unlink fails as above, rmdir with STATUS_DIRECTORY_NOT_EMPTY or STATUS_NOT_A_DIRECTORY); a symlink to a file is still removed by unlink.
- Set
LIBSMB2_COMMITandLIBSMB2_TARBALL_SHA256inscripts/shared/_versions.sh, andLIBSMB2_RELEASEto the nextrN. - Build: every patch applies again to the new tree, and one whose anchor moved stops the build. Drop the patches upstream now carries.
- Run
./build sources, thendart run tool/ffigen.dartanddart run tool/bindings_drift.dartindart_smb2. - Push a
release/branch and publish as in 4.4.
- "Go not found": install Go and run again.
- A Linux, Windows or Verify run fails right away: Docker isn't running.
- macOS and iOS are greyed out: they need a Mac with Xcode. GitHub Actions can build them instead.
- The first Docker build is slow: it is building the image; later builds reuse it until
docker/Dockerfilechanges. - "libsmb2 tarball SHA-256 mismatch": the cached download is stale or corrupt. Delete
sources/cache/and build again. - A patch fails with "anchor not found": upstream moved under the patch after a bump. Update the anchor in the named
patch_*.py. - "Android NDK 28.2.13676358 not found" or "the build is pinned to": install that NDK with the SDK Manager, set
NDK_HOME, or build withALLOW_ANY_NDK=1. - "N of the 9 release assets" from the manifest:
release_builds/lacks some libraries. Build them, take the CI artifact, or useALLOW_PARTIAL=1for a manifest that is not a release.
- libsmb2's
lib/andinclude/are licensed under the LGPL-2.1 or later; the texts are inlicenses/libsmb2/. The code the patches add to libsmb2 is under the same terms. - Each
libsmb2-rNrelease, from r9, shipsCOPYINGandLICENCE-LGPL-2.1.txtbeside the binaries and inside the xcframework zips. - Each
libsmb2-rNrelease names, in itsmanifest.json, the commit of this repository it was built from: that commit, with the upstream tarball it pins, is the corresponding source of the release's binaries. - The scripts, the patch scripts themselves and the TUI are BSD-3-Clause, see LICENSE.
The build pipeline, the Go TUI and the patches for libsmb2 were written with Claude Code.
Developed by Alessandro Di Ronza