kotlin-native-nuget Help

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

packNuget

Packages the Kotlin/Native shared library as a NuGet package

the shared-lib link tasks, kspKotlin{Target}, nugetReportDiagnostics, nugetCompileInterop, nugetGenerateShims (only if the project also binds a dependency), and nugetSnapshotVersion/nugetSnapshotVersionProps (only when snapshot = true)

nugetReportDiagnostics

Reports declarations the forward bridge could not generate

kspKotlin{Target}

nugetCompileInterop

Compiles the generated C# bindings with dotnet. Runs as part of check and before packNuget stages them

kspKotlin{Target}, nugetGenerateShims (only if the project also binds a dependency)

publishNugetTo<Name>Repository

Pushes the packed .nupkg to the named repositories { nuget("<name>") { } } feed

packNuget

publishNuget

Runs every push task in the project

every PublishNugetTask in the project

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:

[nuget] The generated C# compile check failed for net10.0 (dotnet build exit code 1). Restore or compiler output: .../Interop.cs(29579,59): error CS0101: The namespace 'TestLibrary' already contains a definition for 'Cat' Build FAILED.

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:

nuget { publish { strictCompileCheck = true } }

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:

./gradlew check '-Pnuget.strictCompileCheck=true'

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

nugetSnapshotVersion

Mints a unique snapshot version for this build

build/nuget-snapshot-version.txt

nugetSnapshotVersionProps

Writes the MSBuild props file pinning the current snapshot version

versionPropsFile, default <rootProject>/build/{packageId}Versions.props

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

nugetGenerateRestoreProject

Generates the synthetic interop.csproj for NuGet dependency resolution

(none)

build/nuget-interop/interop.csproj

nugetRestore

Runs dotnet restore to download declared NuGet packages, retrying up to 3 attempts when a feed is unavailable (NU1301)

nugetGenerateRestoreProject

build/nuget-interop/obj/project.assets.json

nugetImport

IDE-sync umbrella task: resolve NuGet dependencies

nugetRestore, plus nugetExtractApi/nugetGenerateBindings/nugetGenerateShims when any dependency binds

(none, umbrella task)

The following three are registered only when at least one dependency declares a bind { } block:

Task

Description

Depends on

Writes

nugetExtractApi

Extracts the public API surface of bound NuGet packages into reverse-ir.json

nugetRestore (reads its project.assets.json)

build/nuget-interop/reverse-ir.json

nugetGenerateBindings

Generates Kotlin stubs and the C# registration contract from reverse-ir.json, and logs every skipped member

nugetExtractApi

build/nuget-interop/kotlin/, build/nuget-interop/NugetDiagnostics.json (report)

nugetGenerateShims

Generates C#-side [UnmanagedCallersOnly] thunks and startup registration shims from reverse-ir.json

nugetExtractApi

build/nuget-interop/csharp/

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.

Last modified: 03 November 2026