Skip to main content

01 - Project Setup

1. Why​

S0 proves the skeleton before any package has content:

  • one Gradle module per Sinew package;
  • four targets (Android, iOS, JVM desktop, wasmJs);
  • a dependency graph that CI enforces;
  • a sample app that runs everywhere.

Getting the structure right first saves fighting it in every later milestone. This note implements the module, target and graph parts of Architecture.

2. Shape​

Targets per module

ModuleandroidiosArm64 + iosSimulatorArm64jvmwasmJs
every library modulelibrarylibrarylibrarylibrary
:sample:sharedlibrary (used by :sample:androidApp)framework SinewSampledesktop applicationbrowser executable

iosX64 is left out (arm64 Macs only), as in Camouflage.

What each module uses (commonMain, plus the platform source sets that differ)

ModuleDepends on (api = exposed to consumers)Platform source sets
:sinew-modelscoroutinesnone
:sinew-exceptionapi(:sinew-models)none
:sinew-l10napi(:sinew-exception), Compose resourcesnone (resources per locale)
:sinew-pagingapi(:sinew-models), api(:sinew-exception), coroutinesnone
:sinew-presentationcoroutinesnone
:sinew-viewmodelapi(:sinew-presentation), api(:sinew-models), :sinew-exception, lifecycle-viewmodel, lifecycle-runtime-compose, Compose runtimenone
:sinew-networkapi(:sinew-exception), api(ktor-client-core), ktor content-negotiation, kotlinx-serialization-json, kotlinx-datetimeandroidMain + jvmMain OkHttp engine, iosMain Darwin engine, wasmJsMain Js engine; network checker per platform
:sinew-securitycoroutinesandroidMain Tink + Keystore + Biometric; iosMain Keychain + platform AES-GCM; jvmMain Tink; wasmJsMain Web Crypto
:sinew-storageapi(:sinew-exception), api(:sinew-security), DataStore (non-web targets)androidMain, iosMain, jvmMain DataStore; wasmJsMain browser storage
:sinew-testingapi of every package it fakes, ktor-client-mock, Turbinenone
:sinew-camouflageapi(:sinew-paging), :sinew-exception, :sinew-l10n, api(camouflage-core)none
:sinew-devtoolsapi(:sinew-network) (for SinewHttpHooks), api(:sinew-storage) (for InspectableStore)none
:sinew-devtools-uiapi(:sinew-devtools), :sinew-l10n, Compose foundation + Material 3none
:sinew-devtools-noopapi(:sinew-network), api(:sinew-storage), Compose runtime: the types its API names, no devtools logic. It replaces both real modules.none

3. API​

3.1 settings.gradle.kts​

rootProject.name = "sinew-kotlin"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

pluginManagement {
includeBuild("build-logic")
repositories { google(); mavenCentral(); gradlePluginPortal() }
}
dependencyResolutionManagement {
repositories { google(); mavenCentral() }
versionCatalogs {
create("kotlinLibs") { from(files("gradle/kotlin.versions.toml")) } // kotlin, coroutines, serialization, datetime
create("ktorLibs") { from(files("gradle/ktor.versions.toml")) } // ktor core, engines, content-negotiation, mock
create("androidxLibs") { from(files("gradle/androidx.versions.toml")) } // lifecycle, datastore, biometric
create("composeLibs") { from(files("gradle/compose.versions.toml")) } // compose multiplatform, resources (l10n), material3 (devtools-ui only)
create("securityLibs") { from(files("gradle/security.versions.toml")) } // tink, cryptography-kotlin
create("toolLibs") { from(files("gradle/tools.versions.toml")) } // turbine, kover, detekt, dokka, vanniktech, agp
}
}
include(
":sinew-models", ":sinew-exception", ":sinew-l10n", ":sinew-paging", ":sinew-presentation", ":sinew-viewmodel",
":sinew-network", ":sinew-security", ":sinew-storage", ":sinew-testing", ":sinew-camouflage",
":sinew-devtools", ":sinew-devtools-ui", ":sinew-devtools-noop", ":sinew-bom",
":sample:shared", ":sample:androidApp",
)

3.2 Convention plugins​

Sinew uses the same srctool convention plugins as Camouflage (com.srctool.kmp.library, com.srctool.compose, com.srctool.publish), configured through the srctool {} extension:

srctool {
namespacePrefix = "com.srctool.sinew"
targets = setOf(Android, Ios, Jvm, WasmJs)
publishing { groupId = "com.srctool.sinew"; repoUrl = "https://github.com/srctool/sinew-kotlin" }
}
Module kindPlugins
plain librarycom.srctool.kmp.library, com.srctool.publish
library with Composethe above + com.srctool.compose
:sinew-bomjava-platform + com.srctool.publish

3.3 The dependency-graph check​

A Gradle task, checkSinewGraph, runs on check. It reads every library module's declared project dependencies and fails if any edge isn't in an allowed list kept next to it (gradle/sinew-graph.txt, one from -> to per line). The list mirrors the base Architecture graph.

// gradle/sinew-graph.txt (excerpt)
sinew-exception -> sinew-models
sinew-storage -> sinew-exception
sinew-storage -> sinew-security
sinew-camouflage -> sinew-paging

3.4 Sample app​

PlatformEntry
Android:sample:androidApp: MainActivity → setContent { SampleApp() }
iOSfun MainViewController() = ComposeUIViewController { SampleApp() }, shown from SwiftUI
Desktopfun main() = application { Window(::exitApplication, title = "Sinew") { SampleApp() } }
Webfun main() = ComposeViewport(document.body!!) { SampleApp() }

SampleApp() uses Camouflage's Minimal skin and one list screen built on Pager → PagingState → toCamo → CamoPagedList. Until S5 it shows a placeholder.

4. Build steps​

  1. Create the repo layout, settings.gradle.kts and the six catalogs.
  2. Bring in the convention plugins. Use the published srctool plugins if they exist by then, otherwise a copy of Camouflage's build-logic.
  3. Create every module from the table, each with one public placeholder type, and the declared dependencies.
  4. Write checkSinewGraph and gradle/sinew-graph.txt. Prove it by adding a forbidden edge (sinew-models -> sinew-network) on a branch and watching check fail.
  5. Create the sample app with its four entry points, and run it on an Android emulator, an iOS simulator, a desktop window and in a browser.
  6. Run the Kotlin Gradle plugin's ABI update task for the first ABI dumps (updateKotlinAbi in the KGP version Camouflage uses; verify the task name at S0), and commit them.
  7. CI: see Testing and CI.

Platform setup (for contributors)​

  • JDK 17, Xcode (iOS only, macOS only), a recent Chrome for the wasm tests.

Done when

  • Every module compiles for every target.
  • checkSinewGraph fails on a forbidden edge and passes on the real graph.
  • The sample opens on Android, iOS, desktop and web.
  • Changing any public signature fails checkKotlinAbi until updateKotlinAbi is run.

5. Edge cases​

CaseDecided behavior
DataStore has no wasmJs target:sinew-storage uses browser storage in wasmJsMain for KeyValueStore, and DataStore everywhere else. The Storage note documents the weaker guarantees on web.
Tink has no iOS or wasm target:sinew-security uses Tink only in androidMain and jvmMain. iOS and web use the platform's AES-GCM (see Security).
A module that needs Compose only for one function (ObserveEffects)It applies com.srctool.compose anyway. Settled in base review: no separate module for it.
AGP's KMP library target changed its DSL name between versions (androidLibrary, then android)The convention plugin looks up whichever exists, so an AGP upgrade doesn't break every module. It also calls withHostTest {}, so commonTest runs as Android host tests.
Flavor + build-type configurations (productionReleaseImplementation) don't exist until AGP creates the variantsThe sample's Android app creates the ones it uses with configurations.maybeCreate, so per-variant devtools dependencies resolve.
The convention plugins aren't published yet at S0Sinew carries a copy of Camouflage's build-logic, and switches to the published plugins when they exist.
Building iOS without a MacNot possible. CI's macOS job covers it.
Decided by default (revisit during implementation)
  • Six version catalogs split by concern (Kotlin, Ktor, AndroidX, Compose, security, tools), matching Camouflage's split-catalog style.
  • checkSinewGraph with an allow-list file, rather than a third-party dependency-analysis plugin. It's small, it reads like the base graph, and it fails hard (base review).
  • A copy of build-logic until the shared convention plugins are published. Two copies drift, so the switch to the published plugins should happen early.