Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
*.iml
*.ipr

# Generated files
# Generated files
bin/
gen/

# Gradle files
# Gradle files
.gradle
.gradle/
build/

# Kotlin
.kotlin/

local.properties

Pods
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ cklib {

## Examples

You can find a [tutorial](https://hackernoon.com/how-to-extend-a-kmm-shared-module-with-cc-code) with a [GitHub Sample](https://github.com/ttypic/kmm-embedded-c) to get a brief understanding of how the library works.
[`sample/`](sample) is a small sample that packages [Monocypher](https://monocypher.org), compiles it with CKlib, binds it with cinterop, and exposes a passphrase-encrypted note API.

You can also find a [tutorial](https://hackernoon.com/how-to-extend-a-kmm-shared-module-with-cc-code) with a [GitHub Sample](https://github.com/ttypic/kmm-embedded-c) to get a brief understanding of how the library works.

Additionally you can see multiple examples of C Klib in use here:
1. [zstd-kmp](https://github.com/square/zstd-kmp) - Packages [ztsd](https://github.com/facebook/zstd), a fast real-time compression algorithm.
Expand Down
61 changes: 61 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# C Klib sample: passphrase-encrypted notes with Monocypher

A sample for C Klib that packages the C library [Monocypher](https://monocypher.org). It's a simple passphrase-encrypted note implementation.

```kotlin
val sealed: ByteArray = SecureNote.seal("hunter2", "meet me at the docks at midnight")
val note: String? = SecureNote.open("hunter2", sealed) // null if the passphrase is wrong
```

C Klib compiles C to LLVM bitcode; cinterop reads the same headers and generates the Kotlin
declarations; the Kotlin/Native compiler links the two together.

## Running the sample

From the repository root, using the root wrapper:
```bash
./gradlew -p sample build # compile every declared target
./gradlew -p sample macosArm64Test # the host-runnable test suite
./gradlew -p sample allMonocypher # just the bitcode, for every target
```

`sample/settings.gradle.kts` uses `pluginManagement { includeBuild("..") }`, so the plugin is built
from this working tree rather than downloaded. No `version` on the plugin id, and no
`include(":sample")` in the root build.

> **Note:** The first run downloads an LLVM toolchain into `~/.cklib` (about 1.6 GB) and Kotlin/Native into
`~/.konan`.

## Notes

* **`srcDirs` defaults to `srcRoot/cpp`, even for `Language.C`.** `headersDirs` defaults to
`srcDirs + srcRoot/headers`. If you want your C in a directory not named `cpp`, override both:

```kotlin
create("monocypher", srcDir = file("src/monocypher")) {
language = CompileToBitcode.Language.C
srcDirs = files("src/monocypher/c")
headersDirs = files("src/monocypher/headers")
}
```

* **`mingwX64`** is deliberately not declared: `secure_random.c` has no Windows branch, and adding one
means `BCryptGenRandom` plus linking `bcrypt.lib`.

* **`Language.C` compiles with `-std=gnu11 -O3 -Wall -Wextra -Werror`, hardcoded.** The only escape
hatch is `compilerArgs`, which is appended after those flags, so `-Wno-error=<specific>` works.
This sample needs **no suppressions at all**: Monocypher 4.0.3 and `secure_random.c` both build
clean. Vendored code that is not warning-clean will stop the build dead, so check before you commit
to a library.

* **Shared `nativeMain` + cinterop needs `kotlin.mpp.enableCInteropCommonization=true`.** Without it,
per-target tasks like `compileKotlinMacosArm64` and `macosArm64Test` work fine, but
`compileNativeMainKotlinMetadata` — which `build` runs — fails with `Unresolved reference
'cinterop'` for every binding. It is in `gradle.properties`.

* **`config.kotlinVersion` must match the Kotlin plugin version.** CKlib resolves the toolchain at
`~/.konan/kotlin-native-prebuilt-<os>-<arch>-<kotlinVersion>` and fails with a bare
`InvocationTargetException` if that directory is missing. On a machine with no Kotlin/Native
installed yet, the bitcode task can therefore fail before anything has had a chance to download it;
running any Kotlin/Native task first (`./gradlew -p sample cinteropMonocypherMacosArm64`) fetches
the distribution and unblocks it.
66 changes: 66 additions & 0 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2026 Touchlab
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

import co.touchlab.cklib.gradle.CompileToBitcode

plugins {
kotlin("multiplatform") version "2.0.0"
id("co.touchlab.cklib")
}

kotlin {
val nativeTargets = listOf(
macosArm64(),
macosX64(),
iosArm64(),
iosSimulatorArm64(),
linuxX64(),
)

nativeTargets.forEach { target ->
target.compilations.getByName("main").cinterops.create("monocypher") {
// cinterop only needs the headers. The compiled code arrives as bitcode from cklib,
// which is why the .def declares no staticLibraries or libraryPaths.
includeDirs(project.file("src/monocypher/headers"))
}
}

sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}

cklib {
config.kotlinVersion = "2.0.0"

create("monocypher", srcDir = file("src/monocypher")) {
language = CompileToBitcode.Language.C

// Both default to a `cpp` subdirectory (headersDirs additionally to `headers`) even for
// Language.C, so point them at the real layout instead of naming a C folder "cpp".
srcDirs = files("src/monocypher/c")
headersDirs = files("src/monocypher/headers")

// No compilerArgs. Language.C compiles with a hardcoded
// `-std=gnu11 -O3 -Wall -Wextra -Werror`, and both Monocypher 4.0.3 and secure_random.c
// build clean under it, so no -Wno-error= escape hatch is needed.
// compilerArgs.addAll(
// listOf(
// )
// )
}
}
8 changes: 8 additions & 0 deletions sample/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kotlin.code.style=official
org.gradle.jvmargs=-Xmx3g

# Required because SecureNote.kt lives in the shared `nativeMain` source set and references cinterop
# declarations. Per-target compilation (`compileKotlinMacosArm64`) works without this, but
# `compileNativeMainKotlinMetadata`, which type-checks the shared source set and runs as part of
# `build`, fails with "Unresolved reference 'cinterop'" unless commonization is on.
kotlin.mpp.enableCInteropCommonization=true
Binary file added sample/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 7 additions & 0 deletions sample/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gradle-wrapper.properties, but no gradlew binary for the sample

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
32 changes: 32 additions & 0 deletions sample/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2026 Touchlab
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

pluginManagement {
// Resolves `co.touchlab.cklib` from the plugin sources in this repository, so the sample
// always exercises the working tree rather than a published release.
includeBuild("..")
repositories {
gradlePluginPortal()
mavenCentral()
google()
}
}

dependencyResolutionManagement {
repositories {
mavenCentral()
google()
}
}

rootProject.name = "cklib-sample"
Loading