Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ gen/
.gradle/
build/

# Kotlin

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fun fact AI caught. This isn't a comment. The # has to be the first character of the line and there is a space here. So it's actually a functioning rule which ignores a file named # Kotlin

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.

huh interesting, ok I updated those comments

.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.
67 changes: 67 additions & 0 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2021 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") {
defFile(project.file("src/nativeInterop/cinterop/monocypher.def"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't think this line is required if the def file name matches the create name above

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.

Good catch I removed it

// 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
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) 2021 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