Skip to content

Change Log

0.8.6 (2026-08-01)

  • Update the API visibility and make internal calls internal: ScaffoldProperties, Versioning, the classes behind the bumpVersion, release, generateMokoStrings and generateBuildConfig tasks, and the companion objects on the ktlint rules. You configure the plugins through scaffold {}, so nothing a build script is meant to use has changed. If yours did call one of these, please open an issue.
  • Stop Android lint failing a module that does not use Compose. The suite turns every lint warning into an error, and it switches off two Compose checks by name. In a module without the Compose checks on its classpath those names mean nothing, so lint reported them as unknown, and the warning became an error that failed the build. Unknown names are now ignored, so a module that does use Compose is unaffected and one that does not builds.
  • Turn on core library desugaring in a multiplatform module only when the desugaring library is declared, matching what an Android module already did. The multiplatform path turned it on either way, so a module that added an Android target failed to build with a message asking for a dependency it never said it needed.
  • Read an environment variable through Gradle rather than directly, so buildConfigField behaves. Gradle could not see the read, so it did not know the value was an input. Changing the variable left the previous value compiled in until something else forced the build to reconfigure.
  • Stop using the lint report settings that the Android Gradle plugin retired. Reports are written either way now, so the settings did nothing except emit a deprecation warning on every build. They wrote reports to a shared folder rather than the module's own; nothing referred to that folder.
  • Publish a documentation site at https://thomaskioko.github.io/app-gradle-plugins/, carrying the API reference, the change log and the release guide. It is rebuilt and published each time a release is tagged, and every pull request rebuilds it and fails if a link or a menu entry points at a page that is not there.
  • Add an installing page to the site, covering what a project needs before the first module builds: the Google repository, the version catalog entries the plugins read, and naming every plugin on the root project so a module can apply one without a version. Each step was pasted into an empty project and built, in the order it appears. The version in the snippets comes from the release being published, so it can never fall behind the way the one in the README did.
  • Add a page for each of the eleven plugins to the site, naming every option in scaffold {}. Twenty of them had never been written down anywhere, among them useCodegen, useFeatureFlagCodegen, useDependencyGuard, useFirebase, manifestPlaceholders and the whole moduleGraph {} block. Each identifier and each parameter was read from the source rather than carried over, and every sample was pasted into an empty project and built.
  • Move the code generation guides and the lint rule list onto the site, so everything is in one place and searchable rather than split across three folders. The two module readme files and the comments in the source now point at the pages rather than at file paths, and the root readme is trimmed to what it should have been: what the project is, where the documentation lives, and how to build it.
  • Ship real API documentation with every artifact. The documentation file attached to each release has been empty every time, because nothing ever generated one. All six artifacts now carry the full reference built from the comments in the source.
  • Document every public declaration and fail the build when one arrives without documentation. Thirty-one were missing, among them the whole moduleGraph {} block, and four references pointed at names that no longer resolved. The reference is now complete, and it stays that way on its own rather than by anyone remembering.
  • Save a list of everything each published artifact exposes, and fail the build when a change does not match the saved list. A change that would break someone else's code now has to be made on purpose, and shows up in review as a difference in that file rather than as a surprise after release.
  • Turn a compiler warning into a build failure across all three builds, and replace two deprecated compiler flags with the settings that succeed them. Warnings had been switched off, which is how the report settings above stayed deprecated without anyone noticing.
  • Check how the plugins themselves are written, using the Android lint checks for Gradle code. They found real faults, listed above. The remaining findings are all one thing, using the root project from a module, which stops Gradle from configuring projects independently. Those are recorded so a new one still fails the build.
  • Add the Apache 2.0 license text at the repository root. Every artifact says it is licensed under Apache 2.0, but the license itself was never committed, so GitHub read the project as having no license.
  • Add the Apache license header to every Kotlin file, so the published sources carry it too. The formatter was switched on for plugins and lint-rules but never told which files to read, so most of the project had never been formatted or checked.

0.8.5 (2026-07-24)

  • Attach the Android host-test task to the linuxTest aggregate. KotlinMultiplatformPlugin wired variant test tasks by iterating KotlinTargetWithTests, but the AGP Kotlin Multiplatform android target does not implement that interface, so a module whose suite lived only in androidHostTest never reached the aggregate and its tests went unrun on CI while the job still reported green. The plugin now attaches testAndroidHostTest for any module that has a src/androidHostTest source set. Modules carrying only commonTest are deliberately left out: those tests already run on the jvm target, and repeating them on the Android host adds runtime without adding coverage. Consumers need no change, though a module that had dark androidHostTest tests will start running them, so expect the aggregate to take longer and to surface failures that were previously hidden. Locked in by a functional test that asserts the dependency edge through linuxTest --dry-run rather than only checking that the task resolves.
  • Apply the unused-return-value checker (-Xreturn-value-checker=check) to production compilations only. The flag was set on the shared compilerOptions, so it reached every compilation including test source sets, where Kotest matchers (shouldBe, shouldNotBeNull, and the like) are annotated must-use yet legitimately discard their returned subject in an assertion. That produced an Unused return value warning on nearly every matcher call across a consumer's test suite. BasePlugin now adds the flag through a task-level configuration that skips Kotlin compile tasks whose name contains test (both unit and instrumented), so production code keeps the check while test compilations build cleanly. Consumers need no change; a module that still wants the check in a specific test source set can add -Xreturn-value-checker=check back on that compile task.
  • Document that @ChildPresenter is the annotation for a presenter that needs a graph but is not a navigation destination. Reaching for @NavDestination to obtain the @GraphExtension also emits a NavDestination.Screen and a NavRouteBinding, and those resolve to nothing when the parent builds the presenter directly rather than routing to it (an app's tab shell, for example), as does the ScreenContent if its composable carries @ScreenUi. @ChildPresenter(scope = HostScope::class, parentScope = ActivityScope::class) emits the graph and its factory and nothing else, and the parent injects <Presenter>ChildGraph.Factory the same way it injected the screen graph factory. The generator already supports this shape, so it needs no codegen change.

0.8.4 (2026-06-27)

  • Add a useFirebase() toggle to the app {} extension that moves the Firebase Gradle wiring out of consumer build files. When a google-services.json is present at the module root or under src/debug or src/release, it applies the Google Services and Firebase Crashlytics Gradle plugins and enables mapping file upload on the release build type; checkouts without the config file skip the wiring so they still build. The Crashlytics Gradle plugin is consumed as a compileOnly dependency for its CrashlyticsExtension type, matching the existing metro and baseline-profile pattern where the consuming project supplies the plugin at runtime via apply false.
  • Support a parent-supplied runtime parameter on @ChildPresenter. When the annotated presenter declares a nested @AssistedFactory (because the parent passes a runtime value such as a show id), the generated <Presenter>ChildGraph now exposes that assisted factory as its property instead of the presenter directly, mirroring the parameterized @NavDestination screen path; the parent injects the generated <Presenter>ChildGraph.Factory, creates the graph with a ComponentContext, and calls create(...) on the exposed factory with the runtime arguments. Plain @Inject child presenters are unchanged — the graph still exposes the presenter directly, so existing param-less children generate byte-identical output. This lets a parent-owned child presenter that needs a runtime argument (for example a per-section Show Details child scoped to a show id) satisfy presenter-needs-codegen-annotation with @ChildPresenter rather than a manual assisted factory plus a ktlint_tvmaniac_unrouted_presenters exemption. The parser reads the nested @AssistedFactory and ChildGraphGenerator emits the factory or presenter property accordingly; locked in by a new parameterized golden alongside the existing flat and embeddable shapes.

0.8.3 (2026-06-21)

  • Remove the tvmaniac:compose-screen-needs-codegen-annotation rule. The rule flagged any @Composable taking a presenter parameter that lacked a UI codegen annotation, but a composable that renders a child presenter and is dispatched directly by its parent host (an embedded section or a reusable component embedded in a screen) is intentionally a plain composable, not a navigation destination. A ktlint rule cannot resolve the presenter's type, so it cannot distinguish such an embedded child UI from a routed screen, which made the requirement (and its ktlint_tvmaniac_unrouted_screens exemption list) wrong for the embeddable-component direction. Only routed presenters (@NavDestination/@AppRoot) and their @ScreenUi/@SheetUi/@TabUi/@AppRootUi composables are codegen-wired; everything else is a plain composable. The ktlint_tvmaniac_unrouted_screens property is removed with the rule.
  • Make the tvmaniac:presenter-needs-codegen-annotation exemption case-insensitive. The rule compared the ktlint_tvmaniac_unrouted_presenters entry against the declared class name with a case-sensitive in check, so once the entry reached the rule in a different case than the declared name (for example lowercased, which is how a LowerCasingPropertyType-declared value can arrive through the Gradle ktlint path) a PascalCase entry silently failed to match. The rule now lowercases the parsed exemption set and compares with the lowercased name, and the property uses a case-preserving base PropertyType. Covered by tests that drive a real .editorconfig through KtLintRuleEngine, including a case-mismatch case that fails before the fix.
  • Document the embeddable / reusable component convention for @ChildPresenter. Setting parentScope to a shared ancestor scope (ActivityScope) contributes the generated factory to a graph every screen descends from, so a component can live in its own module and be embedded by any host (or nested in another component). The generator is scope-agnostic, so this needs no codegen change; the @ChildPresenter golden coverage now locks in both the flat (pinned) and embeddable shapes.
  • Add a moduleGraph {} extension that generates a per-module Mermaid dependency graph via graphDump/graphUpdate tasks, with configurable module and configuration filtering.
  • Integrate the dependency-guard plugin to baseline resolved dependencies. AppPlugin guards releaseRuntimeClasspath automatically; other modules opt in via scaffold { useDependencyGuard(...) }.

0.8.2 (2026-06-20)

  • Clean up experimental Kotlin compiler flags. Remove the -Xannotation-default-target, -Xcontext-parameters, -Xannotation-target-all, -Xallow-reified-type-in-catch, and -Xexplicit-backing-fields compiler flags now that these features are stable in Kotlin 2.4. Passing them had become redundant and made every consumer module emit a redundant for the current language version 2.4 warning on each compile task. Projects that pin kotlin-language below 2.4 can pass any flag they still need directly.
  • Rename ignoreUnused() to ignoreUnusedDependencies() (breaking) and scope it to exclude the named project dependencies from the calling module's unused-dependency check, instead of silencing the named modules' own analysis output. This lets scaffold { ignoreUnusedDependencies(...) } stand in for a hand-written project-scoped configure<DependencyAnalysisSubExtension> block when a module declares a dependency as api(...) for downstream consumers (for example integration-test fixtures) that its own sources never reference.

0.8.1 (2026-06-20)

  • ignoreAll() now defaults to the project it is called from when no path is passed, so a bare ignoreAll() inside a module's scaffold {} block silences that module's dependency analysis. The empty vararg previously silenced nothing, which left framework umbrella modules such as ios-framework flagged for every dependency the iOS application consumes across the Objective-C boundary.
  • Exclude codegen-featureflag-annotations from the unused-dependency analysis. The @FeatureFlag annotation has source retention and is consumed by KSP, so dependency analysis cannot see it in bytecode and reported it as unused on every module that calls useFeatureFlagCodegen().

0.8.0 (2026-05-19)

  • Introduce a KSP-based feature-flag codegen that eliminates the per-flag qualifier and DI binding boilerplate in Metro KMP projects. A single @FeatureFlag object anchor generates the Metro @Qualifier and the Set<FeatureFlag<Boolean>> binding, and a platform field scopes a flag to one platform at compile time.
  • Bump Kotlin to 2.4.0.
  • Refactor dependency analysis exclusions to a DSL-based configuration
  • tvmaniac:no-manual-nav-binding flags manual bindings @Provides @IntoSet providers returning NavRoot, NavRootBinding, NavDestination, or NavRouteBinding. The codegen processor owns those multibindings whenever a presenter is annotated with @NavDestination or @AppRoot, so any hand-written contribution duplicates the generated provider.

0.7.9 (2026-05-17)

  • Fix dependency analysis configuration

0.7.8 (2026-05-10)

Three additions extend the codegen surface so consumers no longer hand-write tab pager renderers, parent-owned child presenter graphs, or the NavRoot multibinding.

  • @TabUi targets a @Composable function as the renderer for one tab pager page. The generated ScreenContent binding mirrors @ScreenUi except the matcher casts the active child to TabChild<*> rather than ScreenDestination<*>. Use it on bottom-bar tab pages whose presenters are wrapped as TabChild rather than pushed onto a stack.
  • @ChildPresenter targets a presenter constructed by another presenter rather than navigated to through a route. The processor emits a <Presenter>ChildGraph graph extension exposing the presenter as a property plus a @ContributesTo(parentScope) @GraphExtension.Factory whose create<BaseName>Graph function takes a ComponentContext and returns the graph. Multiple children may share a scope; each gets its own graph extension and its own factory function (the unique name avoids return-type collisions when both factories contribute to the same parent scope).
  • @NavDestination(kind = TAB_ROOT) now also contributes the route singleton itself into Set<NavRoot>. Consumers no longer need to keep a hand-written <Feature>RootBinding next to each tab to populate that set. The new contribution lives inside the existing <Presenter>TabDestinationBinding file alongside the NavDestination<*> and NavRootBinding<*> entries.

See annotations.md, examples.md sections 4, 7, and 8, and architecture/consumer-contract.md for the full surface.

Lint rules

  • tvmaniac:presenter-needs-codegen-annotation now accepts @ChildPresenter as a satisfying annotation, so child presenters routed through the new codegen path no longer need an editorconfig exemption.

Fix

  • Apply com.autonomousapps.dependency-analysis per subproject.
  • Default appAuthRedirectScheme test manifest placeholder on KMP Android targets so transitive AppAuth dependencies do not break processAndroidHostTestManifest / processAndroidDeviceTestManifest.

0.7.7 (2026-05-10)

Two new annotations cover the application's root host. The pair eliminates the manual @BindingContainer that consumers used to write for the root presenter and the multi-argument call site every activity used to make to render the root composable.

  • @AppRoot targets an @AssistedInject presenter implementation. The processor reads the nested @AssistedFactory, infers the bound interface from the implementation's supertypes, and emits a <InterfaceName>BindingContainer that contributes @Provides @SingleIn(parentScope) for the bound interface. Consumers replace their hand-written root binding container with one annotation.
  • @AppRootUi targets the host @Composable function. The processor reads the function's non-modifier parameters and emits an AppRootProvider interface plus a @Composable AppRootProvider.AppRootContent(modifier) extension. Consumers make their activity-scope @DependencyGraph extend the generated AppRootProvider, and the activity invokes graph.AppRootContent() instead of forwarding each dependency by hand.

The codegen now publishes @SingleIn, @Composable, and Modifier in addition to the previously-published Metro and Decompose constants. See annotations.md, examples.md sections 7 and 8, and architecture/consumer-contract.md for the full surface.

Lint rules

Two new ktlint rules enforce that the codegen annotations above are actually applied. Forgetting either annotation now fails the build at lint time rather than slipping through to runtime.

  • tvmaniac:presenter-needs-codegen-annotation flags any top-level class whose name ends with Presenter and is annotated with @Inject or @AssistedInject but is missing both @NavDestination and @AppRoot. Classes carrying a @Contributes... Metro annotation are exempt; child presenters routed through a manual @GraphExtension opt out via the new ktlint_tvmaniac_unrouted_presenters editorconfig property.
  • tvmaniac:compose-screen-needs-codegen-annotation flags any top-level @Composable function with a presenter or rootPresenter parameter that is missing @ScreenUi, @SheetUi, and @AppRootUi. Tab-root screens dispatched manually inside a parent host opt out via the new ktlint_tvmaniac_unrouted_screens editorconfig property.

KSP

  • Register the kspCommonMainKotlinMetadata output (build/generated/ksp/metadata/commonMain/kotlin) as a commonMain Kotlin source directory for KMP projects, and wire every KotlinCompilationTask and per-target ksp* task to depend on it. Resolves IDE Unresolved reference warnings for KSP-generated symbols (Metro @GraphExtension, navigation codegen) referenced from commonMain.
  • useCodegen() now registers the navigation codegen processor only on kspCommonMainMetadata for KMP projects (via the new addKspDependencyForCommonMain helper). Combined with the metadata srcDir registration above, this avoids duplicate generation that would otherwise occur when per-target ksp tasks reprocessed commonMain sources.

0.7.6 (2026-05-09)

A single annotation now covers every navigation destination. The three older annotations (@NavScreen, @TabScreen, @NavSheet) have been replaced by @NavDestination(kind = ...), and the processor auto-detects whether a presenter accepts a runtime parameter from the route.

  • Replace @NavScreen, @TabScreen, and @NavSheet with a single @NavDestination(kind = ...) annotation. The processor reads one annotation on the presenter and picks the destination role from the kind parameter: SCREEN for stack screens, OVERLAY for modal sheets and dialogs, TAB_ROOT for top level tab anchors.
  • Auto-detect parameterized presenters from a nested Metro's @AssistedFactory. A presenter with a nested factory generates a binding that reads the route property and threads it through factory.create(...). A plain @Inject presenter generates a binding that exposes the presenter directly. Annotating a tab presenter with @AssistedInject produces a compile error because a tab's route is a singleton data object and carries no payload.

Lint rules

A new published lint-rules artifact ships six ktlint rules that enforce the project's navigation, preview, dependency injection, and test naming conventions. Two of the rules read .editorconfig so consumer projects can adapt the navigation layer location and the set of forbidden preview wrappers without forking.

  • New lint-rules artifact (io.github.thomaskioko.gradle.plugins:lint-rules) shipping six custom ktlint rules:
    • tvmaniac:no-mutating-router-import prevents Decompose router mutation imports outside the navigation layer. The two read only types (ChildStack, ChildSlot) remain allowed everywhere.
    • tvmaniac:no-navigation-construct-outside-nav prevents StackNavigation() and SlotNavigation() construction outside the navigation layer. Type references in parameter and return positions are unaffected.
    • tvmaniac:no-custom-navigator-interface prevents feature specific *Navigator interfaces. Presenters must inject the canonical Navigator or SheetNavigator from navigation/api.
    • tvmaniac:no-style-wrapper-in-preview prevents redundant styling wrappers (TvManiacTheme, TvManiacBackground, Surface, MaterialTheme by default) inside @Preview composables. The wrapper provider applies the project styling once.
    • tvmaniac:metro-redundant-inject removes redundant @Inject from classes that already declare a Metro @Contributes... annotation. Autocorrect-able.
    • tvmaniac:test-name-format enforces the should X given Y test naming convention. Backticked and camelCase forms are both accepted.
  • The navigation and preview rules read .editorconfig properties (ktlint_tvmaniac_navigation_module_paths, ktlint_tvmaniac_preview_wrappers, ktlint_tvmaniac_preview_wrapper_packages) so consumer projects can adjust the navigation layer location and the set of forbidden wrappers without forking. See the lint rules documentation.

Misc

  • Bumped ktlint dependency 1.4.0 → 1.8.0.
  • Dependency updates.

0.7.5 (2026-04-26)

  • Apply KT-42254 Kotlin/Native cache-disable workaround automatically in addIosTargetsWithXcFramework
  • io.github.thomaskioko.gradle.plugins.root is now required on the root project. Any subproject plugin in this suite (app, android, jvm, multiplatform, base) throws GradleException at apply-time when root is missing on the root project.
  • Add id("io.github.thomaskioko.gradle.plugins.root") to the root build.gradle.kts plugins block.
  • Removed per-subproject application of com.autonomousapps.dependency-analysis from AndroidPlugin. The plugin is applied only at the root via RootPlugin; consumers that previously relied on the leaked transitive must apply RootPlugin explicitly.
  • Introduced typed ScaffoldProperties layer; all Gradle property keys consumed by the plugins are centralized in PropertyKeys.
  • Aggregate test tasks (linuxTest, iosTest, ciTest) are now registered only on the root project. Subproject plugins (AndroidPlugin, JvmPlugin, KotlinMultiplatformPlugin) attach their variant test tasks to the root aggregates via dependsOn.
  • Removed dead SpotlessPlugin.shouldConfigureXmlFormatting method.
  • AndroidPlugin no longer throws when the lint bundle is absent from the consumer's version catalog. The lint-check bundle is now correctly optional via the new getBundleDependenciesOrNull helper (matches existing getDependencyOrNull).

0.7.4 (2026-04-25)

  • Apply manifest placeholders to device tests

0.7.3 (2026-04-21)

  • Add manifestPlaceholders DSL

0.7.2 (2026-04-18)

  • Add support for UI renderer bindings

0.7.1 (2026-04-17)

  • Introduce a KSP-based navigation codegen that eliminates per-destination DI boilerplate in Metro + Decompose KMP projects.

0.6.7 (2026-04-10)

  • Remove SKIE DSL

0.6.6 (2026-04-09)

  • Fix iOS test crashes for modules using moko resources.
  • Copy moko resource bundles to iOS test binaries so localization tests run on iOS.
  • Optimize MokoResourceGeneratorTask: remove dead incremental logic, reduce duplication, scope compile dependency to commonMain.
  • Add golden file tests for moko resource code generation.

0.6.4 (2026-04-01)

  • Add Metro Gradle plugin and configure contribution providers.
  • Add support for Compose UI tests.
  • Dependency updates.

0.6.3 (2026-03-26)

  • Remove Tag publishing from beta builds

0.6.2 (2026-03-25)

  • Add support for beta version bumps.
  • Update AppPlugin to use BUILD_NUMBER and enable release optimizations.

0.6.1 (2026-03-24)

  • Push release tags to origin automatically during release.

0.6.0 (2026-03-24)

  • Add release automation tasks (bumpVersion, release).
  • Dependency updates.

0.5.1 (2026-02-26)

  • Enable new compiler features: explicit backing fields, reified types in catch clauses, and @all: annotation target.
  • Fix dependency analysis plugin configuration for KMP projects.
  • Replace deprecated -Xjvm-default compiler flag with -jvm-default.
  • Dependency updates.

0.5.0 (2026-02-12)

  • Migrate to gradle 9.0
  • Delete AndroidMultiplatformPlugin plugin. We now only need KotlinMultiplatformPlugin
  • Remove org.jetbrains.kotlin.android
  • Delete deprecated androidTarget support.

0.4.3 (2025-12-30)

  • Enable return-value-checker.
  • Enable explicitApi by default and remove it from base extension.
  • Update java-toolchain version to 25.
  • Dependency updates.

0.4.2

  • Rename useSkies() to useSkie()
  • Fix typo in release key alias property (rReleaseKeyAliasreleaseKeyAlias)
  • Make MokoResourceGeneratorTask package name configurable via resourcePackage property
  • Improve configuration cache compatibility and task disabling performance
  • Add validation for release signing properties and BuildConfig package name
  • Remove duplicate -Xconsistent-data-class-copy-visibility compiler argument

0.4.1

  • Dependency updates.

0.4.0

  • Add "io.github.thomaskioko.gradle.plugins.buildconfig" plugin. Helps us read keys from local.properties

Usage

buildConfig {

    packageName.set("com.thomaskioko.tvmaniac.core.base")
    booleanField("IS_DEBUG", true)
    buildConfigField("TMDB_API_KEY")
    buildConfigField("CLIENT_ID")
    buildConfigField("CLIENT_SECRET")
}

0.3.2

  • Added useSkie() to DSL
  • Group Kotlin, KSP & Skie in Renovate
  • Enable Kotlin/Native binary size optimization (Experimental) - Reduces release binary size
  • Simplify DisableTasks and fix library lint configuration

0.3.0

  • CI/CD automation
  • Enable tests in multiplatform by default
  • Configure ktlint & spotless.
  • Fix formatting on files

0.2.1

  • Add debug flag and disable tasks during development.
  • Update dependencies.

0.1.0

  • Initial stable release
  • Maven Central publishing support

Plugin Inventory

  • com.thomaskioko.gradle.app - Android application modules
  • com.thomaskioko.gradle.android - Android library modules
  • com.thomaskioko.gradle.android.multiplatform - Android targets in KMP
  • com.thomaskioko.gradle.jvm - JVM modules
  • com.thomaskioko.gradle.multiplatform - Kotlin Multiplatform modules
  • com.thomaskioko.gradle.base - Base configurations
  • com.thomaskioko.gradle.root - Root project setup
  • com.thomaskioko.gradle.baseline.profile - Android baseline profiles
  • com.thomaskioko.gradle.spotless - Code formatting with Spotless
  • com.thomaskioko.resource.generator - Moko resource generation