Building
Prerequisites
- Flutter on PATH (currently 3.47.2 stable).
- Linux fallback:
export PATH="$PATH:~/develop/flutter/bin"(scripts/niman.shdoes this itself).
- Linux fallback:
- Android SDK (Linux):
~/Android/Sdkvia gitignoredandroid/local.properties(never commit). -
First build needs network (the
sqlite3package fetches a prebuilt native binary via Dart build hooks — required on Windows, which ships nosqlite3.dll). - Android NDK 29.0.13113456 (required by
whisper_ggml). - The APK is 64-bit only (
arm64-v8a,x86_64):armeabi-v7ais excluded at packaging inandroid/app/build.gradle.kts. -
Linux desktop build: GTK 3 and the plugins’ system libraries — the list CI installs, in
check.ymlandrelease.ymlalike:sudo apt-get install -y clang cmake git ninja-build pkg-config \ libgtk-3-dev liblzma-dev libstdc++-13-dev \ libayatana-appindicator3-dev libsecret-1-dev libjsoncpp-dev \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-devTwo more are for the checks rather than the build:
xvfb, which gives the integration suite the display the GTK bundle needs on a headless host, andhunspellwithhunspell-en-usandhunspell-it, without which the live cases oftest/unit/spell_check_test.dartskip. - Windows desktop build: Visual Studio with the Desktop development
with C++ workload. The installer additionally needs Inno Setup 6 —
choco install innosetup, asrelease.ymldoes.
Helper scripts
Terse output; full logs in /tmp/niman/niman-<cmd>.log
(%TEMP%\niman\ on Windows):
./scripts/niman.sh analyze # flutter analyze --fatal-infos
./scripts/niman.sh test # flutter test (unit + widget)
./scripts/niman.sh check # analyze + test; run before every commit
./scripts/niman.sh integration # integration_test/; run before every commit (issue #241)
./scripts/niman.sh apk # Android release APK
./scripts/niman.sh apk beta # Android testing build (issue #106)
./scripts/niman.sh linux # Linux release bundle
./scripts/niman.sh linux beta # Linux testing build (its own data folder)
scripts\niman.bat check # Windows equivalent
scripts\niman.bat integration # the headless integration_test/ files (sync_e2e needs Linux)
scripts\niman.bat apk beta # Android testing build, from Windows
scripts\niman.bat windows # Windows build (on a Windows host)
scripts\niman.bat windows beta # Windows testing build (its own data folder)
check (.github/workflows/check.yml) runs the formatting gate, analyze,
flutter test and the integration files on every pull request, so the
suite cannot rot unnoticed again (issue #241). The WebDAV sync flow needs
a device (flutter test -d linux), which CI provides on a virtual
display, and CI installs hunspell so the live spell-check cases run there
instead of skipping (issue #363).
site (.github/workflows/site.yml) builds the documentation site on
every pull request with the deploy’s own steps — the Jekyll build from
pages.yml, the step that wraps Niman’s {{…}} template syntax in
/ included. Publishing runs on main alone
(issue #441), so without it a layout, a Liquid tag or a _config.yml key
Jekyll refuses would surface when the site deploys, after the merge
(issue #470). It deploys nothing: contents: read, no artifact.
The two Android builds (issue #106)
The app ships in two flavors of the channel dimension, so the
official app and the testing build install side by side on the same
device. Once a flavor dimension has a flavor, AGP drops the no-flavor
variant, so the official app is the explicit official flavor — no
application ID suffix, no per-flavor manifest, the ID stays
dev.niman.niman:
./scripts/niman.sh apk(orscripts\niman.bat apkon a Windows host) builds the official APK:flutter build apk --release --flavor official, artifactbuild/app/outputs/flutter-apk/app-official-release.apk.flutter runon an Android device needs the flavor too:flutter run --flavor official.
./scripts/niman.sh apk beta (or scripts\niman.bat apk beta on a
Windows host) builds the testing build: the release pipeline plus
the beta product flavor.
- Application ID
dev.niman.niman.beta(the flavor’sapplicationIdSuffixinandroid/app/build.gradle.kts), launcher label “Niman (Testing)” (per-flavor manifestandroid/app/src/beta/AndroidManifest.xml). - Release-equivalent: same build type, signing, SDK, R8 and desugaring — only the ID and the label differ.
- The flavor passes
--dart-define=APP_CHANNEL=testing; the Dart side (lib/src/core/app_channel.dart) hides the Updates settings section and never starts the update scheduler, whatever the stored auto-update toggle says. A build that forgets the define reads asreleaseand keeps full update behavior. - The two installs are fully independent by design: separate storage,
separate SQLite indexes, separate
MANAGE_EXTERNAL_STORAGEgrant (the permission is per application ID, so grant it to the testing install too). - AGP forbids flavor names starting with
test(reserved for test variants), hencebeta.
A local rebuild after a commit uses beta by default; the official
artifacts are built only when asked (see AGENTS.md).
The desktop testing build
On Windows and Linux both builds are one niman executable under one
product name, so without help they share one support folder — one
settings database, one set of indexes, one single-instance claim. A
testing build that migrated the database to a newer schema broke the
installed release, which took the version back down and left the next
migration to redo what was done (2026-09-22). So the testing build keeps
its own folder:
scripts\niman.bat windows beta(./scripts/niman.sh linux beta) passes--dart-define=APP_CHANNEL=testing.appSupportDirectory()(lib/src/core/app_channel.dart) answers a sibling of the platform folder,…\dev.niman\niman-testingon Windows, for everything the app keeps:niman.db,indexes/, the log, the single-instance claim. Libraries have to be opened once again in the testing build; the notes themselves are the same files.- The window and the tray say “Niman (testing)”, and the two can run at the same time.
- The settings database refuses to be opened by a build older than the one that last migrated it, rather than let drift stamp it back down.
CI (.github/workflows/release.yml) builds the official APK from every
release tag and never the testing flavor: a release page holding both
APKs handed the in-app update the testing one (0.0.10; see
releasing). Build it where it is wanted, with
./scripts/niman.sh apk beta.
Checks before every commit
dart fix --apply, thendart format lib test tool(both idempotent); CI fails the job on drift (dart format --output=none --set-exit-if-changed lib test tool)../scripts/niman.sh check(infos are fatal:flutter analyze --fatal-infos).flutter testrunstest/unit/+test/widget/; single test:flutter test test/unit/<f>.dart --plain-name "<name>".integration_test/is E2E, not part of the default run:app_bootandsync_e2erun in CI too (sync_e2eon-d linux, under xvfb);template_backlinkis a repro harness and skips unless a library is seeded at/tmp/niman/repro_lib../scripts/niman.sh integrationruns all three (issue #241).- Windows note: ~20 tests fail on path separators and temp-dir cleanup
(pre-existing, green on Linux). Use
pwsh scripts/newfail.ps1— it prints only failures not inscripts/known-failures.txt(exit 1 = something new broke;-Updaterewrites the baseline).
Codegen
Two drift databases in lib/src/db/, .g.dart committed:
AppDatabase(app_database.dart): settings, long migration chain.IndexDatabase(index_database.dart): one per library, schema 1, no migrations — delete the file to rebuild.
After schema/DAO changes: dart run build_runner build.