Skip to content

Commit c6bc354

Browse files
Copilotalexandru
andcommitted
Fix: Restore Arrow, KAML libraries and use proper native crypto
- Re-add Arrow libraries (arrow-core, arrow-fx-coroutines, arrow-fx-stm, arrow-suspendapp) - Keep KAML for YAML parsing (has native support) - Add KCrypto library for proper HMAC-SHA256/SHA1 implementation - Replace Commons Codec with KCrypto HMAC (native-compatible) - Replace Logback with kotlin-logging (native support) - Remove HOCON support (JVM-only ConfigFactory) - Use native file I/O (POSIX fopen/fgets) instead of java.io.File - Use native process execution (popen/pclose) instead of Runtime.exec() - Keep all Arrow idioms (Either, SuspendApp, etc.) - Maintain original code structure with minimal changes for native compatibility Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
1 parent 464491d commit c6bc354

9 files changed

Lines changed: 541 additions & 242 deletions

File tree

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ build/
77
# Native image stuff
88
**/src/main/resources/META-INF/native-image
99

10-
# Old JVM source directories (migrated to Kotlin/Native)
11-
src/main/
12-
src/test/
10+
# Backup files from migration
1311
*.jvm-backup
1412

1513
### STS ###

build.gradle.kts

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
2-
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
32

43
plugins {
54
alias(libs.plugins.kotlin.multiplatform)
@@ -35,12 +34,33 @@ kotlin {
3534
sourceSets {
3635
val nativeMain by getting {
3736
dependencies {
38-
implementation(libs.kotlinx.coroutines.core)
39-
implementation(libs.kotlinx.serialization.json)
37+
// Arrow libraries with native support
38+
implementation(libs.arrow.core)
39+
implementation(libs.arrow.fx.coroutines)
40+
implementation(libs.arrow.fx.stm)
41+
implementation(libs.arrow.suspendapp)
42+
43+
// Ktor with native support
4044
implementation(libs.ktor.server.core)
4145
implementation(libs.ktor.server.cio)
4246
implementation(libs.ktor.server.html.builder)
47+
implementation(libs.ktor.serialization.kotlinx.json)
48+
49+
// Serialization
50+
implementation(libs.kotlinx.serialization.json)
51+
implementation(libs.kaml)
52+
53+
// CLI
4354
implementation(libs.clikt)
55+
56+
// Coroutines
57+
implementation(libs.kotlinx.coroutines.core)
58+
59+
// Crypto for HMAC
60+
implementation(libs.kcrypto)
61+
62+
// Logging - using kotlin-logging with native support
63+
implementation(libs.kotlin.logging)
4464
}
4565
}
4666

@@ -71,14 +91,45 @@ tasks {
7191
}
7292
}
7393

74-
// Helper task for development
75-
tasks.register("runNativeBinary") {
76-
dependsOn("linkReleaseExecutableNative")
77-
group = "application"
78-
description = "Build the native executable"
79-
doLast {
80-
val binPath = "build/bin/native/releaseExecutable/github-webhook-listener.kexe"
81-
println("Native binary built at: $binPath")
82-
println("Run with: ./$binPath <config-file>")
94+
// kotlin {
95+
// jvmToolchain(22)
96+
// }
97+
98+
tasks {
99+
withType<JavaCompile>().configureEach {
100+
options.release.set(21)
101+
}
102+
103+
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
104+
compilerOptions {
105+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
106+
javaParameters.set(true)
107+
}
108+
}
109+
110+
named<DependencyUpdatesTask>("dependencyUpdates").configure {
111+
fun isNonStable(version: String): Boolean {
112+
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) }
113+
val regex = "^[0-9,.v-]+(-r)?$".toRegex()
114+
val isStable = stableKeyword || regex.matches(version)
115+
return isStable.not()
116+
}
117+
118+
rejectVersionIf {
119+
isNonStable(candidate.version) && !isNonStable(currentVersion)
120+
}
121+
checkForGradleUpdate = true
122+
outputFormatter = "html"
123+
outputDir = "build/dependencyUpdates"
124+
reportfileName = "report"
125+
}
126+
127+
test {
128+
}
129+
}
130+
131+
ktor {
132+
fatJar {
133+
archiveFileName.set("github-webhook-listener-fat.jar")
83134
}
84135
}

settings.gradle.kts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ pluginManagement {
1010
dependencyResolutionManagement {
1111
versionCatalogs {
1212
create("libs") {
13+
version("arrow", "2.2.0")
1314
version("clikt", "5.0.3")
1415
version("coroutines", "1.10.2")
16+
version("kaml", "0.104.0")
1517
version("kotlin", "2.2.21")
18+
version("kotlinLogging", "7.0.13")
1619
version("ktlint", "14.0.1")
1720
version("ktor", "3.3.2")
1821
version("serialization", "1.9.0")
22+
version("suspendapp", "2.2.0")
1923
version("versions", "0.53.0")
2024

2125
// https://plugins.gradle.org/plugin/org.jetbrains.kotlin.multiplatform
@@ -41,6 +45,8 @@ dependencyResolutionManagement {
4145
.versionRef("ktor")
4246
library("ktor-server-html-builder", "io.ktor", "ktor-server-html-builder")
4347
.versionRef("ktor")
48+
library("ktor-serialization-kotlinx-json", "io.ktor", "ktor-serialization-kotlinx-json")
49+
.versionRef("ktor")
4450

4551
// https://github.com/JLLeitschuh/ktlint-gradle
4652
plugin("ktlint", "org.jlleitschuh.gradle.ktlint")
@@ -50,6 +56,30 @@ dependencyResolutionManagement {
5056
plugin("versions", "com.github.ben-manes.versions")
5157
.versionRef("versions")
5258

59+
// https://arrow-kt.io/
60+
library("arrow-core", "io.arrow-kt", "arrow-core")
61+
.versionRef("arrow")
62+
library("arrow-fx-coroutines", "io.arrow-kt", "arrow-fx-coroutines")
63+
.versionRef("arrow")
64+
library("arrow-fx-stm", "io.arrow-kt", "arrow-fx-stm")
65+
.versionRef("arrow")
66+
// https://arrow-kt.io/ecosystem/suspendapp/
67+
library("arrow-suspendapp", "io.arrow-kt", "suspendapp")
68+
.versionRef("suspendapp")
69+
70+
// https://github.com/charleskorn/kaml
71+
library("kaml", "com.charleskorn.kaml", "kaml")
72+
.versionRef("kaml")
73+
74+
// https://github.com/oshai/kotlin-logging
75+
library("kotlin-logging", "io.github.oshai", "kotlin-logging")
76+
.versionRef("kotlinLogging")
77+
78+
// Crypto for HMAC (native support)
79+
version("kcrypto", "5.4.0")
80+
library("kcrypto", "com.soywiz.korlibs.krypto", "krypto")
81+
.versionRef("kcrypto")
82+
5383
// https://github.com/ajalt/clikt
5484
library("clikt", "com.github.ajalt.clikt", "clikt")
5585
.versionRef("clikt")

src/nativeMain/kotlin/org/alexn/hook/AppConfig.kt

Lines changed: 21 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
package org.alexn.hook
44

5+
import arrow.core.Either
6+
import com.charleskorn.kaml.Yaml
7+
import com.charleskorn.kaml.YamlConfiguration
58
import kotlinx.cinterop.ExperimentalForeignApi
69
import kotlinx.cinterop.toKString
710
import kotlinx.serialization.ExperimentalSerializationApi
811
import kotlinx.serialization.Serializable
9-
import kotlinx.serialization.json.Json
1012
import platform.posix.fclose
1113
import platform.posix.fgets
1214
import platform.posix.fopen
@@ -43,13 +45,13 @@ data class AppConfig(
4345

4446
companion object {
4547
@OptIn(ExperimentalForeignApi::class)
46-
fun parseFile(filePath: String): Result<AppConfig> {
48+
fun parseFile(filePath: String): Either<ConfigException, AppConfig> {
4749
val extension = filePath.substringAfterLast('.', "").lowercase()
4850

4951
val content = try {
5052
readFile(filePath)
5153
} catch (ex: Exception) {
52-
return Result.Error(
54+
return Either.Left(
5355
ConfigException(
5456
"Failed to read configuration file: $filePath",
5557
ex,
@@ -58,113 +60,40 @@ data class AppConfig(
5860
}
5961

6062
return when (extension) {
61-
"json" -> parseJson(content)
62-
"yaml", "yml" -> {
63-
// For now, we'll convert simple YAML to JSON
64-
// Full YAML parsing would require a native YAML library
65-
parseSimpleYaml(content)
66-
}
63+
"yaml", "yml" -> parseYaml(content)
6764
else ->
68-
Result.Error(
65+
Either.Left(
6966
ConfigException(
70-
"Unsupported configuration file format: $extension",
67+
"Unsupported configuration file format: $extension (only YAML/YML supported for native)",
7168
),
7269
)
7370
}
7471
}
7572

76-
fun parseJson(string: String): Result<AppConfig> =
73+
fun parseYaml(string: String): Either<ConfigException, AppConfig> =
7774
try {
78-
val config = jsonParser.decodeFromString(
79-
serializer(),
80-
string,
81-
)
82-
Result.Success(config)
83-
} catch (ex: Exception) {
84-
Result.Error(
85-
ConfigException(
86-
"Failed to parse JSON configuration",
87-
ex,
75+
Either.Right(
76+
yamlParser.decodeFromString(
77+
serializer(),
78+
string,
8879
),
8980
)
90-
}
91-
92-
// Simple YAML parser for basic configurations
93-
// This is a simplified version that handles the basic structure
94-
private fun parseSimpleYaml(yaml: String): Result<AppConfig> {
95-
try {
96-
val lines = yaml.lines().filter { it.isNotBlank() && !it.trim().startsWith("#") }
97-
val json = buildString {
98-
append("{")
99-
var inHttp = false
100-
var inProjects = false
101-
var currentProject: String? = null
102-
var indent = 0
103-
104-
for ((index, line) in lines.withIndex()) {
105-
val trimmed = line.trim()
106-
val currentIndent = line.takeWhile { it == ' ' }.length
107-
108-
when {
109-
trimmed.startsWith("http:") -> {
110-
if (index > 0) append(",")
111-
append("\"http\":{")
112-
inHttp = true
113-
inProjects = false
114-
currentProject = null
115-
}
116-
trimmed.startsWith("projects:") -> {
117-
if (inHttp) append("}")
118-
append(",\"projects\":{")
119-
inHttp = false
120-
inProjects = true
121-
currentProject = null
122-
}
123-
inHttp && trimmed.contains(":") -> {
124-
val (key, value) = trimmed.split(":", limit = 2)
125-
val cleanValue = value.trim().trim('"')
126-
if (trimmed != lines.first { it.contains("http:") }) append(",")
127-
append("\"${key.trim()}\":${if (cleanValue.toIntOrNull() != null) cleanValue else "\"$cleanValue\""}")
128-
}
129-
inProjects && currentIndent == 2 && trimmed.contains(":") && !trimmed.contains(" ") -> {
130-
// Project name
131-
if (currentProject != null) append("}")
132-
val projectName = trimmed.removeSuffix(":")
133-
if (currentProject != null) append(",")
134-
append("\"$projectName\":{")
135-
currentProject = projectName
136-
}
137-
currentProject != null && trimmed.contains(":") -> {
138-
// Project property
139-
val (key, value) = trimmed.split(":", limit = 2)
140-
val cleanValue = value.trim().trim('"')
141-
if (trimmed != lines.first { it.contains("$currentProject:") }.let { lines.indexOf(it) + 1 }.let { if (it < lines.size) lines[it] else trimmed }) append(",")
142-
append("\"${key.trim()}\":\"$cleanValue\"")
143-
}
144-
}
145-
}
146-
if (currentProject != null) append("}")
147-
if (inProjects) append("}")
148-
append("}")
149-
}
150-
151-
return parseJson(json)
15281
} catch (ex: Exception) {
153-
return Result.Error(
82+
Either.Left(
15483
ConfigException(
15584
"Failed to parse YAML configuration",
15685
ex,
15786
),
15887
)
15988
}
160-
}
16189

162-
private val jsonParser =
163-
Json {
164-
isLenient = true
165-
ignoreUnknownKeys = true
166-
explicitNulls = false
167-
}
90+
private val yamlParser =
91+
Yaml(
92+
configuration =
93+
YamlConfiguration(
94+
strictMode = false,
95+
),
96+
)
16897

16998
@OptIn(ExperimentalForeignApi::class)
17099
private fun readFile(path: String): String {
@@ -193,9 +122,3 @@ class ConfigException(
193122
message: String,
194123
cause: Throwable? = null,
195124
) : Exception(message, cause)
196-
197-
// Simple Result type to replace Arrow's Either
198-
sealed class Result<out T> {
199-
data class Success<T>(val value: T) : Result<T>()
200-
data class Error(val exception: Exception) : Result<Nothing>()
201-
}

0 commit comments

Comments
 (0)