The nuget {} DSL
Reference for the nuget {} extension.
nuget { }
Member | Configures | Required |
|---|---|---|
|
| optional, omit for a consume-only project |
|
| optional, omit for a publish-only project |
A project can declare either block, both, or neither meaningfully. publish {} alone publishes a package with no reverse bindings; dependencies {} alone binds C# packages into Kotlin without publishing anything. See Gradle tasks for exactly which tasks each combination registers.
publish { }
Configures NugetPublishConfig. Every field is a nullable String with no default. Nothing in the DSL itself enforces they're set, but packNuget fails once it reads an unset one.
Property | Type | Required | Maps to |
|---|---|---|---|
|
| yes |
|
|
| yes |
|
|
| yes |
|
|
| yes |
|
|
| yes | the Kotlin package the generated C# namespaces are rooted at; sub-packages map relative to it. Also the default export scope: see below |
| function | no | empty; when set, only these package prefixes (and their sub-packages) are bridged |
| function | no | empty; a package prefix or a qualified declaration name (a class, object, sealed base, or top-level function, plus everything nested under it); applied after |
|
| no |
|
|
| no |
|
|
| no |
|
Snapshot versioning
With snapshot = true, every packNuget run mints a fresh, immutable version instead of reusing version as-is, so a .NET consumer's next restore always sees a new version and never serves NuGet's cached copy of the previous build:
The minted version (1.0.0-snapshot.<epochMillis>) is pinned in an MSBuild props file under a property name derived from packageId: every character outside [A-Za-z0-9_] is dropped, a leading digit gets a _ prefix, and Version is appended (MyCatLib becomes MyCatLibVersion). See Publish a Kotlin/Native library as NuGet for the full local-iteration flow and the consumer-side props import.
Multi-RID packages
packNuget only links the native targets this host's Kotlin/Native toolchain can build, but a package worth publishing needs every RID your CI matrix produces. prebuiltRuntimes points at a directory built by another host and merges it into this host's own pack, so one packNuget run still produces one package covering both:
A typical two-host CI flow: a Windows leg runs packNuget and uploads its staged runtimes/ folder as an artifact, then a macOS leg downloads it, points prebuiltRuntimes at it, and runs packNuget itself, producing one package with both RIDs.
packNuget validates the merge rather than silently dropping anything:
A locally linked RID with no
.dll/.dylib/.soto copy fails the build naming the RID and the directory scanned.A
prebuiltRuntimesdirectory with no RID subdirectories, or a RID subdirectory whosenative/folder is missing or empty, fails naming the expected layout (<prebuiltRuntimes>/<rid>/native/*.dll|*.dylib|*.so).The same RID arriving both locally linked and prebuilt fails naming both sources: pick one producer per RID.
A prebuilt RID name this plugin version does not know how to build is a warning only, since the RID set NuGet accepts is open and the tree may come from a newer plugin.
Export scoping
By default NugetProcessor bridges every public declaration in the module. include/exclude scope that down: a package prefix match (pkg == p || pkg.startsWith("$p.")), with exclude always winning over include. exclude additionally matches a qualified declaration name and everything nested under it: exclude("com.contoso.api.Shape") drops the Shape class (and, for a sealed base, its subclasses) from the export set, so a member that references it is skipped with a named diagnostic and the rest of the package still bridges. include stays package-level.
When include is empty, the effective include set defaults to rootPackage if set, otherwise to everything. This makes rootPackage a scoping knob, not just a renaming one: a public declaration outside rootPackage is not bridged unless named explicitly in include. A scope that admits none of the module's public declarations (for example a stray include("kotlin")) warns once with SKIPPED_ALL_DECLARATIONS instead of silently shipping a package with no Interop.cs.
A package that is only reached via dependencies { dependency(...) { bind { } } } (the reverse stub packages generated for a consumed C# dependency) is always exported regardless of include/exclude, since a module that both publishes forward and consumes a NuGet dependency needs those bound types reachable from its own forward return types to keep compiling.
Cross-module export closure
include/exclude/rootPackage also decide what crosses a Gradle module boundary. The export set is a reachability closure from the module's own admitted declarations: the processor walks return types, parameter types, property types, type arguments of an admitted carrier (Flow<T>, List<T>/Set<T>/Map<K,V>), sealed subclasses, and primary-constructor parameter types, and admits every discovered declaration through the same include/exclude/rootPackage predicate, whether it lives in this module or in a dependency module pulled in with implementation(project(":models")). No separate DSL verb is needed: a :models module under the same rootPackage is admitted automatically.
A dependency-module type the closure refuses to admit is skipped, and the diagnostic names the actual reason (outside the effective include set, excluded, cross-module admission off entirely, or an expect declaration whose actualization lives in the dependency and can't be brought into scope at all) with an actionable fix for each. See Publishing Kotlin to C# for the full set of messages.
A dependency type nested inside another declaration (Broadcast.Schedule) is declared nested under its owner in the generated C#, exactly like a module-local nested type, once the owner (Broadcast) is admitted. A member naming only the nested type still admits the owner (climbing the chain first), and a declared nested type's own member types are walked too, so Broadcast.Schedule.timetable(): Timetable admits the top-level dependency type Timetable on the strength of a member declared two levels down. A dependency type nested under a still-deferred owner shape (inner class, a generic, an enum class, an interface, or a sealed base/arm) is refused admission outright, the same as a module-local one under the same deferred shape; see Classes and objects: Nested types.
Every cross-namespace type reference in the generated Interop.cs is global::Namespace.Name qualified, so the file compiles with no using needed regardless of how many packages a namespace-crossing member touches:
dependencies { }
Configures a NugetDependencyScope, whose only member is dependency(...).
dependency(id, version = null) { }
Parameter | Type | Required |
|---|---|---|
|
| yes, the NuGet package id |
|
| no, omit for an unpinned |
Inside the trailing block, NugetDependency exposes:
Property / function | Type | Required | Notes |
|---|---|---|---|
|
| no | same as the |
|
| no | an extra NuGet feed URL, added to |
| function, configures | no | omit to resolve the dependency without generating any Kotlin bindings for it |
bind { }
Configures NugetBindConfig. Declaring bind {} at all is what triggers nugetExtractApi, nugetGenerateBindings, and nugetGenerateShims for that dependency. See Gradle tasks.
Property / function | Type | Required | Default |
|---|---|---|---|
|
| no | the dependency id, lowercased with |
| function | no | empty, with no |
| function | no | empty |
| function | no | none |
include/exclude match a C# namespace exactly, or any of its sub-namespaces (ns == filter or ns.startsWith("$filter.")); when both match the same namespace, exclude wins. alias maps one specific C# namespace to a Kotlin package, overriding both packageName and the id-derived default for that namespace only.
Only one bind { } block is supported per dependency (a second call overwrites the first). For what actually gets bound once a namespace is included, see Consuming C# in Kotlin.