Project structure & builds
A Day project combines a Cargo package, a Day.toml app manifest, and platform host projects.
Rust code defines the app’s shared behavior; the host projects provide the files needed by
Xcode, Gradle, and other platform build tools. The CLI builds them from the layout below.
The conventional project
my-app/
├── Day.toml # the app manifest: id, title, targets, window (name/version come from Cargo.toml)
├── Cargo.toml # a normal Cargo package (bin + rlib)
├── build.rs # day-build codegen: the typed res:: constants src/lib.rs includes
├── src/
│ ├── lib.rs # the app: pieces, signals, routes; res::locales::install()
│ └── main.rs # desktop entry point; mobile entries live in lib.rs macros
├── resource/
│ ├── assets/ # arbitrary data files → res::assets::stations_json
│ ├── images/ # processed images → res::images::logo, logo@2x.png variants
│ ├── vectors/ # SVG glyphs, staged natively per backend → res::vectors::…
│ ├── fonts/ # custom fonts (.ttf/.otf), referenced by family name
│ ├── icons/ # the app icon master; `day prepare` renders every platform's set under build/day/host
│ └── locales/
│ ├── en/app.ftl # Fluent translations, embedded at compile time; a new
│ └── fr/app.ftl # directory here is a new language (res::locales::install())
├── dayscript/ # dayscript flows: walkthroughs, screenshots, assertions
├── store/ # the canonical store listing `day store` consumes
├── website/ # optional app-site scaffold (skip with --no-website)
├── platform/
│ ├── ios/ # Xcode scaffold: DayApp.xcodeproj + a thin Swift Runner
│ └── android/ # Gradle scaffold: settings/app modules, AndroidManifest, theme
├── platform/ohos/ # HarmonyOS scaffold: hvigor ArkTS host + sign-hap.mjs
└── build/day/ # generated: cargo target dirs, staged resources, screenshots
Day.tomlis the single manifest. The app’s Day-specific identity (id,title,artifact,build), its declaredtargets, the default window geometry, and the[permissions],[signing], and[sbom]sections live here, whilenameandversionare derived from Cargo.toml’s[package], so they can never drift. The identity properties (id,title,artifact,build) can be overridden per platform ([app.ios]), per toolkit ([app.qt]), or per target ([app.macos-appkit]); the platform scaffolds read the resolved values at build time.- The scaffolds are host projects.
platform/ios,platform/android, andplatform/ohoscontain no app logic. Each is a minimal native shell that loads the Rust library and hands it the root view. They rarely change. - Generated files are written to
build/day/: Cargo target directories (one per target and profile, so parallel builds never contend), staged resources, packed artifacts, and dayscript screenshots all live under one ignorable directory.
How a build works
Every target follows the same shape. day build -p <target> (or launch, which builds first)
stages resources, selects the toolkit feature, and runs the platform’s build system for
anything native:
day build -p <target>
│
├── 1. stage resources resource/images + resource/assets → the target's native store
│ (actool / aapt2 / GResource / .qrc / rawfile — see below)
│
├── 2. select features --features <toolkit> + every standalone piece's
│ <piece>/<toolkit> renderer feature (from cargo metadata)
│
└── 3. platform build
├── desktop cargo build → the app binary is the artifact
├── ios xcodebuild → Runner.app (links the cargo staticlib)
├── android cargo-ndk + gradle → app.apk (bundles the cargo cdylib)
└── harmony cargo + hvigor + sign → app.hap (bundles the cargo cdylib)
One backend is compiled per binary. The AppKit build contains no GTK code, the Android build only its JNI bridge. Standalone pieces (say, a Lottie or map piece) contribute their own native code and dependencies through Cargo metadata, so the app never re-declares per-piece build wiring.
Desktop: macos-appkit, linux-gtk, linux-qt, windows-xaml, and the GTK/Qt combinations
Desktop targets are the simplest: the artifact is the Cargo binary itself.
src/*.rs ──► cargo build -p my-app --features appkit (per-target CARGO_TARGET_DIR)
│
├── GTK: links system GTK 4 / libadwaita
├── Qt / XAML: cc-compiled C++ shim (built by the toolkit crate's build.rs)
├── XAML: embeds a side-by-side manifest (XAML Islands requires it)
└── macos-appkit: swift build prepass when Swift is contributed
(build/day/macos/DayPieces, statically linked — docs/swiftui)
▼
build/day/cargo/<target>/<profile>/my-app ◄── day launch runs this directly
▼
day pack: macOS .app + ad-hoc codesign + .dmg
Because GTK and Qt are portable, macos-gtk, macos-qt, windows-gtk, and windows-qt build the
same way on their respective hosts. Resources that need a native compiler (GResource, .qrc) are
compiled if the tool is on PATH and otherwise fall back to filesystem loading, so a missing
glib-compile-resources never fails the build.
iOS: ios-uikit
The Xcode project owns the bundle; the Rust code arrives as a static library through a build-phase
callback into the day CLI:
day build -p ios-uikit
│
├── generate DayPieces a local SwiftPM package assembled from every piece's
│ [package.metadata.day.ios]: Swift shims, SwiftPM deps
│ (remote or local packages), system frameworks, and the
│ platform floor. A local package's public SwiftUI views
│ are scanned and exported as crate::swiftui::… bindings;
│ [package.metadata.day.macos] is the appkit twin
│
└── xcodebuild platform/ios/DayApp.xcodeproj (Runner target, iphonesimulator arm64)
│
├── script phase: "day xcode-backend build"
│ └── cargo rustc --crate-type staticlib --target aarch64-apple-ios-sim
│ → libmy_app.a, linked into Runner
├── actool: resource/images → Media.xcassets → optimized Assets.car
└── Swift Runner: loads the Day root view, hands control to Rust
▼
build/day/ios-uikit/Debug-iphonesimulator/MyApp.app
▼
xcrun simctl install booted … && simctl launch (day launch)
The callback design means opening platform/ios in Xcode and pressing Run also works, because the
build phase calls back into day for the Rust half.
Xcode build settings
Both Xcode scaffolds keep their editable build settings in a committed
platform/<ios|macos>/DayApp.xcconfig rather than inside the .xcodeproj, so they read and diff as
plain text. Every build configuration uses it as its base configuration, and it ends with two
optional includes:
#include? "../../build/day/xcconfig/ios.xcconfig" # generated from Day.toml by `day build`
#include? "DayApp.local.xcconfig" # per-checkout, gitignored
The generated file carries the bundle id, version, and build number, so Day.toml stays
authoritative once a build has run. DayApp.local.xcconfig is included after it and overrides both,
which is where a signing team belongs:
DEVELOPMENT_TEAM = ABCDE12345
CODE_SIGN_STYLE = Automatic
The scaffold sets no signing keys of its own, so simulator and local desktop builds sign ad-hoc and
need no Apple account. A device build takes the team from the local file, which every .gitignore
covers, so a fork signs with its own team and its own bundle id without editing a tracked file.
Settings that day build and day pack pass on the command line override all three.
Android: android-mdc
On Android, day runs Cargo first, then hands Gradle a project whose source sets already point at
everything Day staged:
day build -p android-mdc
│
├── cargo-ndk (arm64-v8a) ────────► build/day/jniLibs/arm64-v8a/libmy_app.so
│
├── piece discovery ──────────────► build/day/android/day-pieces.json
│ (each piece's Java dirs, Gradle deps, Maven repos,
│ manifest permissions — read generically by Day's Gradle plugin)
│
└── gradle assembleDebug platform/android/
│
├── sourceSets: the day-android Java shim + piece Java + jniLibs + assets/
├── aapt2: staged resource/images → res/drawable* → R.drawable ids
└── Material 3 theme + DayActivity host (loads the .so, calls nativeStart)
▼
platform/android/app/build/outputs/apk/debug/app-debug.apk
▼
adb install … && am start DayActivity (day launch)
Day’s Gradle plugin configures that project from build/day/android/
(The Gradle project). A build started from Android
Studio packages the Rust .so from the last day build.
HarmonyOS: harmony-arkui
The newest pipeline follows the Android shape with HarmonyOS tooling: an ArkTS host project in
platform/ohos/, a cross-compiled NAPI library, and a post-build signing step with the public
OpenHarmony development certificate:
day build -p harmony-arkui
│
├── cargo rustc --crate-type cdylib --target x86_64-unknown-linux-ohos (emulator; arm64 device)
│ linker = $OHOS_NDK_HOME/llvm/bin/<triple>-clang
│ ────────► platform/ohos/entry/libs/<abi>/libentry.so
│
├── hvigor assembleHap platform/ohos/ (ohpm install first)
│ ├── compiles the ArkTS host (Index.ets mounts Day via a NodeContent slot)
│ ├── packs libentry.so + resources/rawfile/day/ (staged images & assets)
│ └── → entry-default-unsigned.hap
│
└── sign-hap.mjs patch compileSdkType → "OpenHarmony", sign with the SDK's
│ public release material (no developer account required)
▼
platform/ohos/entry/build/…/my-app-signed.hap
▼
hdc install … && aa start EntryAbility (day launch)
Web: web-dom
This is the shortest of the five pipelines, and the only one with no host project to check in. The app’s lib crate is compiled straight to wasm and dropped next to the host page:
day build -p web-dom
│
├── cargo rustc --crate-type cdylib --target wasm32-unknown-unknown
│ exports day_dom_main (via day::day_start_web!)
│ ────────► dist/app.wasm
│
├── the host trio, embedded in the CLI and written out verbatim
│ ────────► dist/index.html · dist/shim.js · dist/day.css
│
└── bundled images and fonts + a fonts.json manifest the shim preloads
────────► dist/assets/…
▼
dist/ is the deployable — copy it to any static host
▼
day launch -p web-dom serves dist/ over loopback and opens your browser
There is no day pack for this target: dist/ is already the artifact. Browsers refuse to
instantiate WebAssembly from file:, so use day launch (or any static server) rather than
opening index.html directly.
How resources are packaged
resource/images/ and resource/assets/ are looked up by name at runtime through the generated
typed constants (image(res::images::logo), resource(res::assets::stations_json); a typo is a
compile error). Before each platform build Day stages the files, unchanged, into that target’s
native resource store, so the platform’s machinery does the optimizing, and the runtime read is
native (and zero-copy wherever the store exposes a stable pointer):
day build -p <target>
│ stage
┌──────────────────────────┼──────────────────────────────┐
▼ ▼ ▼
resource/images/logo.png resource/assets/stations.json resource/icons/
│ │ │
│ per-target store │ per-target store │ dock / taskbar /
│ │ │ launcher icon
┌─────┴──────────────────┐ ┌─────┴─────────────────────┐ │
│ iOS Assets.car │ │ Apple bundle file+mmap │ ▼
│ macOS bundle file │ │ Android AAssetManager │ .icns / mipmap /
│ Android res/drawable* │ │ GTK GResource │ .ico / xcassets
│ GTK GResource │ │ Qt QResource │
│ Qt .qrc │ │ XAML loose file │
│ XAML scale-*.png │ │ ArkUI rawfile fd+mmap │
│ ArkUI rawfile │ └───────────┬───────────────┘
└─────┬──────────────────┘ │
▼ ▼
image(res::images::logo) resource(res::assets::stations_json)
native by-name lookup zero-copy &[u8] view, random access
At runtime, resource() returns a Resource backed directly by that store:
let res = day::resource(res::assets::stations_json).expect("bundled");
let bytes: &[u8] = res.as_slice(); // zero-copy view into the native store
let mut header = [0u8; 16];
res.read_at(0, &mut header); // random access, no allocation
On Apple platforms that view is an mmap of the bundle file; on Android it is the NDK
AAssetManager buffer of an uncompressed asset; GTK and Qt read out of resource blobs compiled
into the binary; ArkUI maps the rawfile descriptor. Images resolve through each platform’s
by-name API (UIImage(named:), R.drawable, gtk_picture_new_for_resource, QPixmap(":/…"),
resource://RAWFILE/…), so density variants like logo@2x.png map onto the platform’s own
scale-selection mechanism.
Fluent translations under resource/locales/ take a different path: they
are embedded into the binary at compile time with include_str!, so locale
switching never touches the filesystem.
The full per-platform details, including the limits (what gets optimized where, and which stores allow zero-copy), are in the resources reference; the HarmonyOS pipeline has its own reference page.