Skip to content

Commit

Permalink
Merge pull request #15 from Animeshz/node-js
Browse files Browse the repository at this point in the history
Add NodeJS support!
  • Loading branch information
Animeshz committed Feb 4, 2021
2 parents 9561d16 + 56a3b65 commit 19b1070
Show file tree
Hide file tree
Showing 62 changed files with 2,259 additions and 771 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ __Notice: Project is on hold till about one month or somewhat longer (I'm busy).

## What is KeyboardMouse.kt

KeyboardMouse.kt is a lightweight, coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin and Java.
KeyboardMouse.kt is a lightweight, coroutine-based multiplatform kotlin library for idiomatically interacting with Keyboard and Mouse (receiving and sending global events) from Kotlin, Java and NodeJS.

We aim to provide high-level as well as high-performant low-level access to such APIs. See the documentation below to
know more!
Expand Down
14 changes: 13 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ plugins {

allprojects {
this.group = "com.github.animeshz"
this.version = "0.2.4"
this.version = "0.3.1"

repositories {
mavenCentral()
jcenter()
}
}

subprojects {
subprojects {
// Address https://github.com/gradle/gradle/issues/4823: Force parent project evaluation before sub-project evaluation for Kotlin build scripts
if (gradle.startParameter.isConfigureOnDemand
&& buildscript.sourceFile?.extension?.toLowerCase() == "kts"
&& parent != rootProject) {
generateSequence(parent) { project -> project.parent.takeIf { it != rootProject } }
.forEach { evaluationDependsOn(it.path) }
}
}
}
24 changes: 24 additions & 0 deletions composite-build-src/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
`kotlin-dsl`
`java-gradle-plugin`
}

repositories {
jcenter()
}

dependencies {
implementation(kotlin("gradle-plugin"))
}

gradlePlugin {
plugins.register("keyboard-mouse-publishing") {
id = "keyboard-mouse-publishing"
implementationClass = "com.github.animeshz.keyboard_mouse.publishing.PublishingPlugin"
}

plugins.register("keyboard-mouse-native-compile") {
id = "keyboard-mouse-native-compile"
implementationClass = "com.github.animeshz.keyboard_mouse.native_compile.NativeCompilationPlugin"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.animeshz.keyboard_mouse.native_compile

class Target(
val os: String,
val arch: String,
val dockerImage: String
)

open class NativeConfiguration {
val jni = JniConfiguration()
val napi = JsCompilationConfiguration()

fun jni(configuration: JniConfiguration.() -> Unit) {
jni.apply(configuration)
}

fun napi(configuration: JsCompilationConfiguration.() -> Unit) {
napi.apply(configuration)
}
}

open class JniConfiguration {
val headers: JniHeaderConfiguration = JniHeaderConfiguration()
val compilation: JniCompilationConfiguration = JniCompilationConfiguration()

fun headers(configuration: JniHeaderConfiguration.() -> Unit) {
headers.apply(configuration)
}

fun compilation(configuration: JniCompilationConfiguration.() -> Unit) {
compilation.apply(configuration)
}
}

open class JniHeaderConfiguration {
var inputDir: String = ""
var outputDir: String = ""
}

open class JniCompilationConfiguration {
var baseInputPaths: List<String> = emptyList()
var outputDir: String = ""
var targets: List<Target> = emptyList()
}

open class JsCompilationConfiguration {
var baseInputPaths: List<String> = emptyList()
var outputDir: String = ""
var targets: List<Target> = emptyList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.github.animeshz.keyboard_mouse.native_compile

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import java.io.ByteArrayOutputStream
import javax.inject.Inject

/**
* For building shared libraries out of C/C++ sources for JVM
*/
open class JniCompilationTask @Inject constructor(
private val target: Target
) : DefaultTask() {
init {
group = "nativeCompilation"
}

@get:Input
@set:Option(option = "verbose", description = "Sets verbosity of output.")
var isVerbose: Boolean = false

@TaskAction
fun run() {
project.checkDockerInstallation()

val tmpVar = project.file(".").absolutePath
val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) {
"/run/desktop/mnt/host/${tmpVar[0].toLowerCase()}${tmpVar.substring(2 until tmpVar.length).replace('\\', '/')}"
} else {
tmpVar
}

val work: () -> Pair<Int, String> = {
ByteArrayOutputStream().use {
project.exec {
commandLine(
"docker",
"run",
"--rm",
"-v",
"$path:/work/project",
target.dockerImage,
"bash",
"-c",
"mkdir -p \$WORK_DIR/project/build/jni && " +
"mkdir -p \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " +
"cd \$WORK_DIR/project/build/tmp/compile-jni-${target.os}-${target.arch} && " +
"cmake -DARCH=${target.arch} \$WORK_DIR/project/src/jvmMain/cpp/${target.os} && " +
"cmake --build . ${if (isVerbose) "--verbose " else ""}--config Release && " +
"cp -rf libKeyboardKt${target.arch}.{dll,so,dylib} \$WORK_DIR/project/build/jni 2>/dev/null || : && " +
"cd .. && rm -rf compile-jni-${target.os}-${target.arch}"
)

isIgnoreExitValue = true
standardOutput = System.out
errorOutput = it
}.exitValue to it.toString()
}
}
var (exit, error) = work()

// Fix non-daemon docker on Docker for Windows
val nonDaemonError = "docker: error during connect: This error may indicate that the docker daemon is not running."
if (Os.isFamily(Os.FAMILY_WINDOWS) && error.startsWith(nonDaemonError)) {
project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue()

do {
Thread.sleep(500)
val result = work()
exit = result.first
error = result.second
} while (error.startsWith(nonDaemonError))
}

System.err.println(error)
if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.github.animeshz.keyboard_mouse.native_compile

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import org.gradle.internal.jvm.Jvm
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import kotlin.system.exitProcess

open class JniHeaderGenerationTask @Inject constructor(
private val configuration: JniHeaderConfiguration
) : DefaultTask() {
val metaRegex = """public \w*\s*class (.+)\.(\w+) (?:implements|extends).*\{\R([^\}]*)\}""".toRegex()
val methodRegex = """.*\bnative\b.*""".toRegex()

init {
group = "nativeCompilation"

inputs.dir(configuration.inputDir)
outputs.dir(configuration.outputDir)

dependsOn(project.tasks.getByName("compileKotlinJvm"))
}

@TaskAction
fun run() {
val javaHome = Jvm.current().javaHome
val javap = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javap") }?.absolutePath ?: error("javap not found")
val javac = javaHome.resolve("bin").walk().firstOrNull { it.name.startsWith("javac") }?.absolutePath ?: error("javac not found")
val buildDir = project.file("build/classes/kotlin/jvm/main")
val tmpDir = project.file("build/tmp/jvmJni").apply { mkdirs() }

buildDir.walkTopDown()
.filter { "META" !in it.absolutePath }
.forEach { file ->
if (!file.isFile) return@forEach

val output = ByteArrayOutputStream().use {
project.exec {
commandLine(javap, "-private", "-cp", buildDir.absolutePath, file.absolutePath)
standardOutput = it
}.assertNormalExitValue()
it.toString()
}

val (packageName, className, methodInfo) = metaRegex.find(output)?.destructured ?: return@forEach
val nativeMethods = methodRegex.findAll(methodInfo).mapNotNull { it.groups }.flatMap { it.asSequence().mapNotNull { group -> group?.value } }.toList()
if (nativeMethods.isEmpty()) return@forEach

val source = buildString {
appendln("package $packageName;")
appendln("public class $className {")
for (method in nativeMethods) {
if ("()" in method) appendln(method)
else {
val updatedMethod = StringBuilder(method).apply {
var count = 0
var i = 0
while (i < length) {
if (this[i] == ',' || this[i] == ')') insert(i, " arg${count++}".also { i += it.length + 1 })
else i++
}
}
appendln(updatedMethod)
}
}
appendln("}")
}

val outputFile = tmpDir.resolve(packageName.replace(".", "/")).apply { mkdirs() }.resolve("$className.java").apply { delete() }.apply { createNewFile() }
outputFile.writeText(source)

project.exec {
commandLine(javac, "-h", project.file(configuration.outputDir).absolutePath, outputFile.absolutePath)
}.assertNormalExitValue()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.github.animeshz.keyboard_mouse.native_compile

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import java.io.ByteArrayOutputStream
import javax.inject.Inject

/**
* For building shared libraries out of C/C++ sources for NodeJS
*/
open class NapiCompilationTask @Inject constructor(
private val target: Target
) : DefaultTask() {
init {
group = "nativeCompilation"
}

@get:Input
@set:Option(option = "verbose", description = "Sets verbosity of output.")
var isVerbose: Boolean = false

@TaskAction
fun run() {
project.checkDockerInstallation()

val tmpVar = project.file(".").absolutePath
val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) {
"/run/desktop/mnt/host/${tmpVar[0].toLowerCase()}${tmpVar.substring(2 until tmpVar.length).replace('\\', '/')}"
} else {
tmpVar
}

val work: () -> Pair<Int, String> = {
ByteArrayOutputStream().use {
project.exec {
commandLine(
"docker",
"run",
"--rm",
"-v",
"$path:/work/project",
target.dockerImage,
"bash",
"-c",
"mkdir -p \$WORK_DIR/project/build/napi && " +
"cmake-js compile --CDARCH=${target.arch} --arch=${target.arch} ${if (isVerbose) "-l=verbose " else ""} -d=\$WORK_DIR/project/src/jsMain/cpp/${target.os} && " +
"cp -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build/{,Release/}KeyboardKt${target.os.capitalize()}${target.arch}.node \$WORK_DIR/project/build/napi 2>/dev/null || : && " +
"rm -rf \$WORK_DIR/project/src/jsMain/cpp/${target.os}/build"
)

isIgnoreExitValue = true
standardOutput = System.out
errorOutput = it
}.exitValue to it.toString()
}
}
var (exit, error) = work()

// Fix non-daemon docker on Docker for Windows
val nonDaemonError = "docker: error during connect: This error may indicate that the docker daemon is not running."
if (Os.isFamily(Os.FAMILY_WINDOWS) && error.startsWith(nonDaemonError)) {
project.exec { commandLine("C:\\Program Files\\Docker\\Docker\\DockerCli.exe", "-SwitchDaemon") }.assertNormalExitValue()

do {
Thread.sleep(500)
val result = work()
exit = result.first
error = result.second
} while (error.startsWith(nonDaemonError))
}

System.err.println(error)
if (exit != 0) throw GradleException("An error occured while running the command, see the stderr for more details.")
}
}

0 comments on commit 19b1070

Please sign in to comment.