Skip to content

Commit 19605ec

Browse files
authored
KTOR-7489 Multi-platform support + kotlinx-rpc (#158)
* Dependency resolution and compilation of all plugins are now handled through gradle during the build phase. This allows for highlighting and auto-complete to work in all the plugin source directories, using only the yaml manifests. * Each plugin can have multiple modules defined, based on some pre-defined modules: server (default), core, and client. These are resolved as separate gradle modules with independent dependencies and sources. * Modules can be referenced from the plugins for dependencies, gradle plugins, and source files. This controls where the generator will send these items. * Deleted some 1.* files and removed support for the old JSON formats. * Moved around tests and related artifacts because of more logic happening during the build phase. * Added ability to disable build systems for plugins when unsupported (i.e., maven, amper) * Moved version variables from yaml to plugins/gradle.properties, so it's easier for dependabot etc. Also added version references from manifest for gradle plugins * Added kotlinx-rpc plugin
1 parent 6ca9996 commit 19605ec

File tree

196 files changed

+2172
-2923
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

196 files changed

+2172
-2923
lines changed

README.md

+17-4
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,28 @@ To add a new plugin, follow these easy steps:
3333
- You can include any number of install files for populating new projects. More information under
3434
[templates/manifest.ktor.yaml](templates/manifest.ktor.yaml). The existing plugin files under the
3535
[plugins](plugins) folder can also be useful reference for introducing new plugins.
36-
- If your plugin artifacts aren't published on Maven Central you may need to add an entry to the `allRepositories()`
37-
function in the [Repositories.kt](buildSrc/src/main/kotlin/io/ktor/plugins/registry/Repositories.kt) file.
3836
<br /><br />
3937
4038
4. **Run `./gradlew buildRegistry`** to test the new files.<br /><br />
4139
4240
5. **Run `./gradlew buildTestProject`** to generate a sample project.
4341
- You can now experiment with a project generated with your plugin.
44-
- Iterate on updating the new files, building the registry, and generating the test project until you're happy with the results.
42+
- Iterate on updating the new files, building the registry, and generating the test project until you're happy with the results.<br /><br />
4543
4644
6. **Create a pull request** with the new changes.
47-
- Once merged, your plugin will be available in the ktor project generator.
45+
- Once merged, your plugin will be available in the ktor project generator.
46+
47+
48+
### Examples
49+
50+
To supplement the [reference templates](templates), we have many plugin examples that can be of some aid when
51+
adding your own.
52+
53+
Here are a few examples to illustrate different techniques that you can apply for your extensions:
54+
55+
| plugin | features |
56+
|-----------------------------------------------------------------------------|------------------------------------------------------|
57+
| [koin](plugins/server/io.insert-koin) | Basic layout with an extra source file |
58+
| [exposed](plugins/server/org.jetbrains/exposed) | Several injections and dependencies |
59+
| [ktor-server-kafka](plugins/server/io.github.flaxoos/ktor-server-kafka-jvm) | Includes configuration files and a custom repository |
60+
| [kotlinx-rpc](plugins/server/org.jetbrains/kotlinx-rpc) | Multi-module layout with core, client, and server |

build.gradle.kts

+65-69
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
@file:Suppress("UnstableApiUsage")
66

77
import io.ktor.plugins.registry.*
8-
import java.nio.file.Paths
8+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
99

10-
val targets by lazy { fetchKtorTargets(logger) }
10+
val ktorReleases = getKtorReleases(logger)
11+
val latestKtor = ktorReleases.last()
12+
val pluginConfigs by lazy { collectPluginConfigs(logger, ktorReleases) }
1113

1214
plugins {
1315
alias(libs.plugins.serialization)
@@ -18,30 +20,64 @@ plugins {
1820
group = "io.ktor"
1921
version = "1.0-SNAPSHOT"
2022

23+
// create build config for each valid release-plugin-target triple
2124
configurations {
22-
for (target in targets)
23-
target.releaseConfigs.forEach(::create)
25+
// create plugin build configs
26+
for (pluginConfig in pluginConfigs) {
27+
create(pluginConfig.name) {
28+
pluginConfig.parent?.let { parent ->
29+
extendsFrom(get(parent))
30+
}
31+
}
32+
}
2433
}
2534

26-
dependencies {
27-
// each ktor version has its own classpath
28-
for (target in targets) {
29-
for ((config, version) in target.releases) {
30-
config("io.ktor:ktor-${target.name}-core:$version")
31-
config(kotlin("stdlib"))
32-
33-
// test imports for test_function template
34-
if (target.name == "server") {
35-
config(kotlin("test"))
36-
config(kotlin("test-junit"))
37-
config("io.ktor:ktor-server-test-host:$version")
35+
repositories {
36+
mavenCentral()
37+
38+
for (repositoryUrl in pluginConfigs.flatMap { it.repositories }.distinct()) {
39+
maven(repositoryUrl)
40+
}
41+
}
42+
43+
sourceSets {
44+
// include all the plugins as source paths, using the latest valid ktor release for each
45+
for (pluginConfig in pluginConfigs.latestByPath()) {
46+
create(pluginConfig.name) {
47+
kotlin.srcDir("plugins/${pluginConfig.path}")
48+
compileClasspath += configurations[pluginConfig.name]
49+
pluginConfig.parent?.let { parent ->
50+
compileClasspath += configurations[parent]
51+
compileClasspath += sourceSets[parent].output
3852
}
53+
}
54+
}
55+
}
3956

40-
for (dependency in target.allArtifactsForVersion(version))
41-
config(dependency)
57+
dependencies {
58+
// create a build config for every plugin-release-module combination
59+
for (pluginConfig in pluginConfigs) {
60+
val type = pluginConfig.type
61+
val release = pluginConfig.release
62+
val config = pluginConfig.name
63+
64+
// common dependencies
65+
config(kotlin("stdlib"))
66+
config(kotlin("test"))
67+
config(kotlin("test-junit"))
68+
config("io.ktor:ktor-$type-core:$release")
69+
when(type) {
70+
"client" -> config("io.ktor:ktor-client-mock:$release")
71+
"server" -> config("io.ktor:ktor-server-test-host:$release")
4272
}
73+
74+
// artifacts for the specific plugin version
75+
for ((group, name, version) in pluginConfig.artifacts)
76+
config("$group:$name:${version.resolvedString}")
4377
}
44-
val latestKtor = targets.first().releases.last().version
78+
79+
// shared sources used in buildSrc
80+
implementation(files("buildSrc/build/libs/shared.jar"))
4581

4682
// current ktor dependencies for handling manifests
4783
implementation("io.ktor:ktor-server-core:$latestKtor")
@@ -67,15 +103,6 @@ dependencies {
67103
testImplementation(kotlin("test"))
68104
}
69105

70-
// include relevant copied classes from buildSrc module
71-
sourceSets {
72-
main {
73-
kotlin {
74-
srcDir("build/copied")
75-
}
76-
}
77-
}
78-
79106
detekt {
80107
toolVersion = libs.versions.detekt.version.get()
81108
config.setFrom(file("detekt.yml"))
@@ -88,19 +115,6 @@ tasks {
88115
useJUnitPlatform()
89116
}
90117

91-
/**
92-
* We copy this shared source file with the parent project because there is otherwise
93-
* a chicken/egg problem with building the project which confuses the IDEA.
94-
*/
95-
val copyPluginTypes by registering(Copy::class) {
96-
group = "build"
97-
from(
98-
"buildSrc/src/main/kotlin/io/ktor/plugins/registry/PluginReference.kt",
99-
"buildSrc/src/main/kotlin/io/ktor/plugins/registry/PluginCollector.kt",
100-
)
101-
into("build/copied")
102-
}
103-
104118
// download all sources
105119
compileKotlin {
106120
compilerOptions {
@@ -109,40 +123,15 @@ tasks {
109123
"-XdownloadSources=true"
110124
)
111125
}
112-
dependsOn(copyPluginTypes)
113126
}
114127

115128
// resolving plugin jars from custom classpaths
116129
val resolvePlugins by registering {
117130
group = "plugins"
118131
description = "Locate plugin resources from version definitions"
119132
doLast {
120-
for (target in targets) {
121-
val resolvedArtifacts = target.releases.associate { release ->
122-
release.version to configurations[release.config].resolvedConfiguration.resolvedArtifacts
123-
}
124-
outputReleaseArtifacts(
125-
outputFile = Paths.get("build/${target.name}-artifacts.yaml"),
126-
configurations = resolvedArtifacts
127-
)
128-
}
129-
}
130-
}
131-
132-
// print jar origins for finding problematic classpath imports
133-
val outputDependencies by registering {
134-
group = "plugins"
135-
description = "Print all artifacts and their origins to deps-server.txt and deps-client.txt"
136-
dependsOn(resolvePlugins)
137-
doLast {
138-
val reportDir = Paths.get("${project.rootDir.absolutePath}/dependencies")
139-
prepareDirectory(reportDir)
140-
141-
for (target in targets) {
142-
val resolvedArtifacts = target.releases.associate { release ->
143-
release.version to configurations[release.config].resolvedConfiguration
144-
}
145-
outputDependencyTrees(reportDir.resolve(target.name), resolvedArtifacts)
133+
writeResolvedPluginConfigurations(pluginConfigs) { configName ->
134+
configurations[configName].resolvedConfiguration
146135
}
147136
}
148137
}
@@ -156,13 +145,20 @@ tasks {
156145
standardInput = System.`in`
157146
}
158147

148+
// compiles ALL build targets before building the registry
149+
val compileAll by registering {
150+
group = "build"
151+
description = "Compile all source sets"
152+
dependsOn(withType<KotlinCompile>())
153+
}
154+
159155
// builds the registry for distributing to the project generator
160156
val buildRegistry by registering(JavaExec::class) {
161157
group = "plugins"
162158
description = "Build the registry from plugin resources"
163159
mainClass = "io.ktor.plugins.registry.BuildRegistryKt"
164160
classpath = sourceSets["main"].runtimeClasspath
165-
dependsOn(resolvePlugins)
161+
dependsOn(resolvePlugins, compileAll)
166162
}
167163

168164
// generates a test project using the modified plugins in the repository

buildSrc/build.gradle.kts

+49
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,67 @@
11
/*
22
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
@file:Suppress("UnstableApiUsage")
45

56
plugins {
67
`kotlin-dsl`
78
alias(libs.plugins.serialization)
89
}
910

11+
val shared = "shared"
12+
13+
configurations {
14+
create(shared)
15+
}
16+
1017
dependencies {
18+
shared(kotlin("stdlib"))
19+
shared(libs.kotlinx.serialization.core)
20+
shared(libs.maven.artifact)
21+
1122
implementation(libs.kaml)
1223
implementation(libs.maven.artifact)
24+
1325
testImplementation(kotlin("test"))
1426
}
1527

28+
sourceSets {
29+
val sharedSrc = create(shared) {
30+
kotlin.srcDir("../shared")
31+
compileClasspath += configurations[shared]
32+
}
33+
main {
34+
compileClasspath += sharedSrc.output
35+
runtimeClasspath += sharedSrc.output
36+
}
37+
test {
38+
compileClasspath += sharedSrc.output
39+
runtimeClasspath += sharedSrc.output
40+
}
41+
}
42+
43+
val packageShared = tasks.register<Jar>("packageShared") {
44+
archiveBaseName.set("shared")
45+
from(sourceSets["shared"].output)
46+
}
47+
48+
// Task to copy output of shared source set to main output
49+
val copySharedOutput = tasks.register<Copy>("copySharedOutput") {
50+
from(sourceSets["shared"].output)
51+
into(sourceSets["main"].output.classesDirs.first { it.toString().contains("kotlin") })
52+
finalizedBy(packageShared)
53+
}
54+
55+
tasks.named("compileKotlin").configure {
56+
finalizedBy(copySharedOutput)
57+
}
58+
59+
for (task in listOf("jar", "pluginUnderTestMetadata", "validatePlugins").map { tasks.named(it) }) {
60+
task.configure {
61+
mustRunAfter(copySharedOutput)
62+
}
63+
}
64+
1665
tasks.withType<Test> {
1766
useJUnitPlatform()
1867
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package io.ktor.plugins.registry
6+
7+
import com.charleskorn.kaml.Yaml
8+
import com.charleskorn.kaml.YamlMap
9+
import com.charleskorn.kaml.parseToYamlNode
10+
import com.charleskorn.kaml.yamlMap
11+
import java.nio.file.Path
12+
import java.nio.file.Paths
13+
import kotlin.io.path.ExperimentalPathApi
14+
import kotlin.io.path.createDirectory
15+
import kotlin.io.path.deleteRecursively
16+
import kotlin.io.path.exists
17+
import kotlin.io.path.inputStream
18+
import kotlin.io.path.isDirectory
19+
import kotlin.io.path.listDirectoryEntries
20+
21+
// Get all directories using patterns like "plugins/client/*/*"
22+
fun folders(pattern: String): List<Path> {
23+
val segments = pattern.split("/").iterator()
24+
if (!segments.hasNext())
25+
return emptyList()
26+
27+
var folders = listOf<Path>(Paths.get(segments.next().ifEmpty { "/" }))
28+
while(segments.hasNext()) {
29+
val next = segments.next()
30+
folders = when(next) {
31+
"*" -> folders.flatMap { folder ->
32+
when {
33+
folder.isDirectory() -> folder.listDirectoryEntries()
34+
else -> emptyList()
35+
}
36+
}
37+
else -> folders.mapNotNull { folder -> folder.resolve(Paths.get(next)).takeIf { it.exists() } }
38+
}
39+
}
40+
return folders.filter { it.isDirectory() }
41+
}
42+
43+
@OptIn(ExperimentalPathApi::class)
44+
fun Path.clear(): Path {
45+
deleteRecursively()
46+
return createDirectory()
47+
}
48+
49+
fun Path.readYamlMap(): YamlMap? =
50+
try {
51+
takeIf { it.exists() }
52+
?.inputStream()
53+
?.use(Yaml.default::parseToYamlNode)
54+
?.yamlMap
55+
} catch (_: Exception) {
56+
null
57+
}

0 commit comments

Comments
 (0)