Change Log¶
0.8.6 (2026-08-01)¶
- Update the API visibility and make internal calls internal:
ScaffoldProperties,Versioning, the classes behind thebumpVersion,release,generateMokoStringsandgenerateBuildConfigtasks, and the companion objects on the ktlint rules. You configure the plugins throughscaffold {}, 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
buildConfigFieldbehaves. 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 themuseCodegen,useFeatureFlagCodegen,useDependencyGuard,useFirebase,manifestPlaceholdersand the wholemoduleGraph {}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
pluginsandlint-rulesbut 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
linuxTestaggregate.KotlinMultiplatformPluginwired variant test tasks by iteratingKotlinTargetWithTests, but the AGP Kotlin Multiplatform android target does not implement that interface, so a module whose suite lived only inandroidHostTestnever reached the aggregate and its tests went unrun on CI while the job still reported green. The plugin now attachestestAndroidHostTestfor any module that has asrc/androidHostTestsource set. Modules carrying onlycommonTestare 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 darkandroidHostTesttests 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 throughlinuxTest --dry-runrather 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 sharedcompilerOptions, 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 anUnused return valuewarning on nearly every matcher call across a consumer's test suite.BasePluginnow adds the flag through a task-level configuration that skips Kotlin compile tasks whose name containstest(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=checkback on that compile task. - Document that
@ChildPresenteris the annotation for a presenter that needs a graph but is not a navigation destination. Reaching for@NavDestinationto obtain the@GraphExtensionalso emits aNavDestination.Screenand aNavRouteBinding, 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 theScreenContentif 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.Factorythe 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 theapp {}extension that moves the Firebase Gradle wiring out of consumer build files. When agoogle-services.jsonis present at the module root or undersrc/debugorsrc/release, it applies the Google Services and Firebase Crashlytics Gradle plugins and enables mapping file upload on thereleasebuild type; checkouts without the config file skip the wiring so they still build. The Crashlytics Gradle plugin is consumed as acompileOnlydependency for itsCrashlyticsExtensiontype, matching the existing metro and baseline-profile pattern where the consuming project supplies the plugin at runtime viaapply 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>ChildGraphnow exposes that assisted factory as its property instead of the presenter directly, mirroring the parameterized@NavDestinationscreen path; the parent injects the generated<Presenter>ChildGraph.Factory, creates the graph with aComponentContext, and callscreate(...)on the exposed factory with the runtime arguments. Plain@Injectchild 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) satisfypresenter-needs-codegen-annotationwith@ChildPresenterrather than a manual assisted factory plus aktlint_tvmaniac_unrouted_presentersexemption. The parser reads the nested@AssistedFactoryandChildGraphGeneratoremits 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-annotationrule. The rule flagged any@Composabletaking apresenterparameter 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 itsktlint_tvmaniac_unrouted_screensexemption list) wrong for the embeddable-component direction. Only routed presenters (@NavDestination/@AppRoot) and their@ScreenUi/@SheetUi/@TabUi/@AppRootUicomposables are codegen-wired; everything else is a plain composable. Thektlint_tvmaniac_unrouted_screensproperty is removed with the rule. - Make the
tvmaniac:presenter-needs-codegen-annotationexemption case-insensitive. The rule compared thektlint_tvmaniac_unrouted_presentersentry against the declared class name with a case-sensitiveincheck, so once the entry reached the rule in a different case than the declared name (for example lowercased, which is how aLowerCasingPropertyType-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 basePropertyType. Covered by tests that drive a real.editorconfigthroughKtLintRuleEngine, including a case-mismatch case that fails before the fix. - Document the embeddable / reusable component convention for
@ChildPresenter. SettingparentScopeto 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@ChildPresentergolden coverage now locks in both the flat (pinned) and embeddable shapes. - Add a
moduleGraph {}extension that generates a per-module Mermaid dependency graph viagraphDump/graphUpdatetasks, with configurable module and configuration filtering. - Integrate the
dependency-guardplugin to baseline resolved dependencies.AppPluginguardsreleaseRuntimeClasspathautomatically; other modules opt in viascaffold { 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-fieldscompiler flags now that these features are stable in Kotlin 2.4. Passing them had become redundant and made every consumer module emit aredundant for the current language version 2.4warning on each compile task. Projects that pinkotlin-languagebelow 2.4 can pass any flag they still need directly. - Rename
ignoreUnused()toignoreUnusedDependencies()(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 letsscaffold { ignoreUnusedDependencies(...) }stand in for a hand-written project-scopedconfigure<DependencyAnalysisSubExtension>block when a module declares a dependency asapi(...)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 bareignoreAll()inside a module'sscaffold {}block silences that module's dependency analysis. The empty vararg previously silenced nothing, which left framework umbrella modules such asios-frameworkflagged for every dependency the iOS application consumes across the Objective-C boundary.- Exclude
codegen-featureflag-annotationsfrom the unused-dependency analysis. The@FeatureFlagannotation 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 callsuseFeatureFlagCodegen().
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
@FeatureFlagobject anchor generates the Metro@Qualifierand theSet<FeatureFlag<Boolean>>binding, and aplatformfield 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-bindingflags manual bindings@Provides @IntoSetproviders returningNavRoot,NavRootBinding,NavDestination, orNavRouteBinding. The codegen processor owns those multibindings whenever a presenter is annotated with@NavDestinationor@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)¶
Navigation¶
Three additions extend the codegen surface so consumers no longer hand-write tab pager renderers, parent-owned child presenter graphs, or the NavRoot multibinding.
@TabUitargets a@Composablefunction as the renderer for one tab pager page. The generatedScreenContentbinding mirrors@ScreenUiexcept the matcher casts the active child toTabChild<*>rather thanScreenDestination<*>. Use it on bottom-bar tab pages whose presenters are wrapped asTabChildrather than pushed onto a stack.@ChildPresentertargets a presenter constructed by another presenter rather than navigated to through a route. The processor emits a<Presenter>ChildGraphgraph extension exposing the presenter as a property plus a@ContributesTo(parentScope) @GraphExtension.Factorywhosecreate<BaseName>Graphfunction takes aComponentContextand returns the graph. Multiple children may share ascope; 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 intoSet<NavRoot>. Consumers no longer need to keep a hand-written<Feature>RootBindingnext to each tab to populate that set. The new contribution lives inside the existing<Presenter>TabDestinationBindingfile alongside theNavDestination<*>andNavRootBinding<*>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-annotationnow accepts@ChildPresenteras a satisfying annotation, so child presenters routed through the new codegen path no longer need aneditorconfigexemption.
Fix¶
- Apply
com.autonomousapps.dependency-analysisper subproject. - Default
appAuthRedirectSchemetest manifest placeholder on KMP Android targets so transitive AppAuth dependencies do not breakprocessAndroidHostTestManifest/processAndroidDeviceTestManifest.
0.7.7 (2026-05-10)¶
Navigation¶
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.
@AppRoottargets an@AssistedInjectpresenter implementation. The processor reads the nested@AssistedFactory, infers the bound interface from the implementation's supertypes, and emits a<InterfaceName>BindingContainerthat contributes@Provides @SingleIn(parentScope)for the bound interface. Consumers replace their hand-written root binding container with one annotation.@AppRootUitargets the host@Composablefunction. The processor reads the function's non-modifier parameters and emits anAppRootProviderinterface plus a@Composable AppRootProvider.AppRootContent(modifier)extension. Consumers make their activity-scope@DependencyGraphextend the generatedAppRootProvider, and the activity invokesgraph.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-annotationflags any top-level class whose name ends withPresenterand is annotated with@Injector@AssistedInjectbut is missing both@NavDestinationand@AppRoot. Classes carrying a@Contributes...Metro annotation are exempt; child presenters routed through a manual@GraphExtensionopt out via the newktlint_tvmaniac_unrouted_presenterseditorconfig property.tvmaniac:compose-screen-needs-codegen-annotationflags any top-level@Composablefunction with apresenterorrootPresenterparameter that is missing@ScreenUi,@SheetUi, and@AppRootUi. Tab-root screens dispatched manually inside a parent host opt out via the newktlint_tvmaniac_unrouted_screenseditorconfig property.
KSP¶
- Register the
kspCommonMainKotlinMetadataoutput (build/generated/ksp/metadata/commonMain/kotlin) as acommonMainKotlin source directory for KMP projects, and wire everyKotlinCompilationTaskand per-targetksp*task to depend on it. Resolves IDEUnresolved referencewarnings for KSP-generated symbols (Metro@GraphExtension, navigation codegen) referenced fromcommonMain. useCodegen()now registers the navigation codegen processor only onkspCommonMainMetadatafor KMP projects (via the newaddKspDependencyForCommonMainhelper). 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)¶
Navigation¶
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@NavSheetwith a single@NavDestination(kind = ...)annotation. The processor reads one annotation on the presenter and picks the destination role from thekindparameter:SCREENfor stack screens,OVERLAYfor modal sheets and dialogs,TAB_ROOTfor 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 throughfactory.create(...). A plain@Injectpresenter generates a binding that exposes the presenter directly. Annotating a tab presenter with@AssistedInjectproduces a compile error because a tab's route is a singletondata objectand 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-rulesartifact (io.github.thomaskioko.gradle.plugins:lint-rules) shipping six custom ktlint rules:tvmaniac:no-mutating-router-importprevents Decompose router mutation imports outside the navigation layer. The two read only types (ChildStack,ChildSlot) remain allowed everywhere.tvmaniac:no-navigation-construct-outside-navpreventsStackNavigation()andSlotNavigation()construction outside the navigation layer. Type references in parameter and return positions are unaffected.tvmaniac:no-custom-navigator-interfaceprevents feature specific*Navigatorinterfaces. Presenters must inject the canonicalNavigatororSheetNavigatorfromnavigation/api.tvmaniac:no-style-wrapper-in-previewprevents redundant styling wrappers (TvManiacTheme,TvManiacBackground,Surface,MaterialThemeby default) inside@Previewcomposables. The wrapper provider applies the project styling once.tvmaniac:metro-redundant-injectremoves redundant@Injectfrom classes that already declare a Metro@Contributes...annotation. Autocorrect-able.tvmaniac:test-name-formatenforces theshould X given Ytest naming convention. Backticked and camelCase forms are both accepted.
- The navigation and preview rules read
.editorconfigproperties (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.rootis now required on the root project. Any subproject plugin in this suite (app,android,jvm,multiplatform,base) throwsGradleExceptionat apply-time whenrootis missing on the root project.- Add
id("io.github.thomaskioko.gradle.plugins.root")to the rootbuild.gradle.ktsplugins block. - Removed per-subproject application of
com.autonomousapps.dependency-analysisfromAndroidPlugin. The plugin is applied only at the root viaRootPlugin; consumers that previously relied on the leaked transitive must applyRootPluginexplicitly. - Introduced typed
ScaffoldPropertieslayer; all Gradle property keys consumed by the plugins are centralized inPropertyKeys. - 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 viadependsOn. - Removed dead
SpotlessPlugin.shouldConfigureXmlFormattingmethod. AndroidPluginno longer throws when thelintbundle is absent from the consumer's version catalog. The lint-check bundle is now correctly optional via the newgetBundleDependenciesOrNullhelper (matches existinggetDependencyOrNull).
0.7.4 (2026-04-25)¶
- Apply manifest placeholders to device tests
0.7.3 (2026-04-21)¶
- Add
manifestPlaceholdersDSL
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 tocommonMain. - 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
AppPluginto useBUILD_NUMBERand 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-defaultcompiler flag with-jvm-default. - Dependency updates.
0.5.0 (2026-02-12)¶
- Migrate to gradle 9.0
- Delete
AndroidMultiplatformPluginplugin. We now only needKotlinMultiplatformPlugin - Remove
org.jetbrains.kotlin.android - Delete deprecated androidTarget support.
0.4.3 (2025-12-30)¶
- Enable return-value-checker.
- Enable
explicitApiby default and remove it from base extension. - Update java-toolchain version to 25.
- Dependency updates.
0.4.2¶
- Rename
useSkies()touseSkie() - Fix typo in release key alias property (
rReleaseKeyAlias→releaseKeyAlias) - Make
MokoResourceGeneratorTaskpackage name configurable viaresourcePackageproperty - Improve configuration cache compatibility and task disabling performance
- Add validation for release signing properties and BuildConfig package name
- Remove duplicate
-Xconsistent-data-class-copy-visibilitycompiler 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 modulescom.thomaskioko.gradle.android- Android library modulescom.thomaskioko.gradle.android.multiplatform- Android targets in KMPcom.thomaskioko.gradle.jvm- JVM modulescom.thomaskioko.gradle.multiplatform- Kotlin Multiplatform modulescom.thomaskioko.gradle.base- Base configurationscom.thomaskioko.gradle.root- Root project setupcom.thomaskioko.gradle.baseline.profile- Android baseline profilescom.thomaskioko.gradle.spotless- Code formatting with Spotlesscom.thomaskioko.resource.generator- Moko resource generation