Gradle tasks
Most tasks the plugin registers live in the nuget task group; publishNuget and the per-repository push tasks live in the publishing group instead, alongside Gradle's own maven-publish tasks. There are two independent registration paths: publishing (publish {}, requires Kotlin Multiplatform) and consuming (dependencies {}). A project can use either, both, or neither.
Publishing (Kotlin → C#)
Registered when the build script declares nuget { publish { } } and the Kotlin Multiplatform plugin is applied. The tasks exist as soon as publish {} is declared, so a missing setting or nothing to pack fails when the task runs, not at configuration.
Task | Description | Depends on |
|---|---|---|
| Packages the Kotlin/Native shared library as a NuGet package | the shared-lib link tasks, |
| Reports declarations the forward bridge could not generate |
|
| Compiles the generated C# bindings with dotnet. Runs as part of |
|
| Pushes the packed |
|
| Runs every push task in the project | every |
publishNuget and its per-repository tasks are registered even with no repositories { } block, so publishNuget is always a valid task name on a project that packs; with no repositories configured it simply does nothing. Neither task is ever up to date: a push is a side effect, not a build output. See Publish a Kotlin/Native library as NuGet for the DSL and credential setup.
packNuget writes the staged package to build/nuget/{packageId}.{version}/ and the zipped .nupkg to build/nuget/{packageId}.{version}.nupkg. When the project also binds a dependency, it merges the reverse-direction C# shims from nugetGenerateShims into the same contentFiles/cs/<tfm>/ folder and pins each bound dependency at its exact resolved version in the .nuspec <dependencies> block.
nugetReportDiagnostics re-emits every skipped-declaration message as a Gradle warning on each packNuget run, so a skipped declaration stays visible even once its generating KSP task is UP-TO-DATE. See Forward overview.
nugetCompileInterop
nugetCompileInterop runs as part of check and before packNuget stages the package. It builds every file it would stage (the KSP-generated Interop.cs, plus any reverse-direction shims) against an exact-version PackageReference for each bound dependency, in an isolated build environment that ignores any global.json, Directory.Build.props/targets, Directory.Packages.props, or NuGet.config elsewhere in the repo. If a generated file does not compile, packNuget fails with the compiler's own output (so does check), for example:
The check compiles one project for every framework from your targetFramework up to the major version of the installed .NET SDK, and logs the set (for example net10.0, net11.0 (SDK 11.0.100)). C# that breaks only on a newer framework fails the build, even though the package still targets just the one targetFramework. A newer SDK than the one you test with is not covered, and the task always re-runs so that installing a newer SDK widens the check.
When dotnet is not found (on PATH, or at nuget.dotnet if you set it; see Prerequisites) or can't run, the task logs a warning and skips the check, so publishing a Kotlin/Native library still needs no .NET SDK. A nuget.dotnet that doesn't point at an executable fails instead of skipping, in check as well as packNuget.
To make a CI that forgot to install the SDK fail instead, opt in to strict mode. With it on, a missing or unusable dotnet fails check and packNuget with the same reason the warning would have given, rather than packing unchecked bindings:
To turn it on from CI without editing the build script, pass the Gradle property nuget.strictCompileCheck, on the command line or in gradle.properties. Quote the whole argument in PowerShell:
The property accepts exactly true or false; any other value fails the build. A value set in the publish {} block always wins, so strictCompileCheck = false in the DSL keeps the check soft even when the property is true. To turn strict mode off again, set the DSL value to false, or unset the property if the DSL doesn't set it.
Registered on every project with a publish {} block. packNuget depends on them only when snapshot = true:
Task | Description | Writes |
|---|---|---|
| Mints a unique snapshot version for this build |
|
| Writes the MSBuild props file pinning the current snapshot version |
|
Both are never up to date, since a snapshot build's whole point is a fresh timestamp every run. See The nuget {} DSL for snapshot/versionPropsFile.
KSP's own kspKotlin{Target} task (registered by the Kotlin Gradle plugin, not this plugin) generates Interop.cs and the Kotlin bridge wrappers. packNuget depends on it but does not register it.
Consuming (C# → Kotlin)
Registered as soon as nuget { dependencies { } } declares at least one dependency, independent of publish {}, and even of Kotlin Multiplatform being applied.
Task | Description | Depends on | Writes |
|---|---|---|---|
| Generates the synthetic interop.csproj for NuGet dependency resolution | (none) |
|
| Runs dotnet restore to download declared NuGet packages, retrying up to 3 attempts when a feed is unavailable ( |
|
|
| IDE-sync umbrella task: resolve NuGet dependencies |
| (none, umbrella task) |
The following three are registered only when at least one dependency declares a bind { } block:
Task | Description | Depends on | Writes |
|---|---|---|---|
| Extracts the public API surface of bound NuGet packages into reverse-ir.json |
|
|
| Generates Kotlin stubs and the C# registration contract from reverse-ir.json, and logs every skipped member |
|
|
| Generates C#-side [UnmanagedCallersOnly] thunks and startup registration shims from reverse-ir.json |
|
|
nugetGenerateBindings's output directory is added as a Kotlin source directory on nativeMain (and, per native target, mingwMain or posixMain), and every kspKotlin{Target} task and every compile*KotlinMetadata task (shared source-set metadata compiles, which IDE import also runs) depends on it, so KSP and the metadata compile see the reverse-generated stubs on a clean build. nugetGenerateShims's C# output is what packNuget merges in when a project both publishes and binds a dependency.
Each run of nugetGenerateBindings and nugetGenerateShims replaces the contents of its output directory, so do not hand-place files in build/nuget-interop/kotlin/ or build/nuget-interop/csharp/.
See the nuget {} DSL reference for the publish {} and dependencies { bind { } } blocks that drive this wiring.