Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit

Permalink
Written task for compiling Napi (NodeJS wrapper) and made it compile …
Browse files Browse the repository at this point in the history
…for Linux properly.

Skip supporting NodeJS on x86 Linux for now, because it is not shipped as 32 bit anyways.

Successfully built Windows packages for NodeJS.
  • Loading branch information
Animeshz committed Mar 28, 2021
1 parent cebc75a commit a7e637e
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ class Target(
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()
Expand All @@ -29,3 +42,9 @@ open class JniCompilationConfiguration {
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
Expand Up @@ -8,40 +8,24 @@ import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import kotlin.system.exitProcess

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

@get:Input
@set:Option(option = "verbose", description = "Configures the URL to be verified.")
@set:Option(option = "verbose", description = "Sets verbosity of output.")
var isVerbose: Boolean = false

var dockerImage: String = ""

private fun check() {
println("Checking docker installation")

val exit = project.exec {
commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker"))
isIgnoreExitValue = true
}.exitValue
if (exit != 0) {
println("Please install docker before running this task")
exitProcess(1)
}
}

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

val tmpVar = project.file(".").absolutePath
val path = if (Os.isFamily(Os.FAMILY_WINDOWS)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ open class JniHeaderGenerationTask @Inject constructor(
val methodRegex = """.*\bnative\b.*""".toRegex()

init {
group = "jni"
group = "nativeCompilation"

inputs.dir(configuration.inputDir)
outputs.dir(configuration.outputDir)
Expand Down
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.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.creating
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.register
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.gradle.kotlin.dsl.withType
import org.gradle.language.jvm.tasks.ProcessResources

class NativeCompilationPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.apply(plugin = "org.jetbrains.kotlin.multiplatform")
val ext = target.extensions.create("configureJni", JniConfiguration::class.java)
val ext = target.extensions.create("nativeCompilation", NativeConfiguration::class.java)

setup(target, ext)
setupJni(target, ext.jni)
setupNapi(target, ext.napi)
}

private fun setup(project: Project, extension: JniConfiguration) {
private fun setupJni(project: Project, extension: JniConfiguration) {
project.afterEvaluate {
with(extension.headers) {
if (inputDir.isEmpty() || outputDir.isEmpty()) return@afterEvaluate
}

val compileJniAll by project.tasks.creating { group = "jni" }
project.tasks.getByName("jvmProcessResources") { dependsOn(compileJniAll) }

val compileJniAll by project.tasks.creating { group = "nativeCompilation" }
val headersTask = project.tasks
.register<JniHeaderGenerationTask>("generateJniHeaders", extension.headers).get()

Expand All @@ -38,17 +38,41 @@ class NativeCompilationPlugin : Plugin<Project> {
.register<JniCompilationTask>("compileJni${target.os.capitalize()}${target.arch.capitalize()}", target)
.also { compileJniAll.dependsOn(it.get()) }
.configure {
dockerImage = target.dockerImage

for (path in extension.compilation.baseInputPaths) inputs.dir(path / target.os)
outputs.dir(extension.compilation.outputDir)

dependsOn(headersTask)
}
}

project.configure<KotlinMultiplatformExtension> {
sourceSets.getByName("jvmMain").resources.srcDir(extension.compilation.outputDir)
tasks.withType<ProcessResources>().named("jvmProcessResources") {
dependsOn(compileJniAll)
from(project.file(extension.compilation.outputDir))
}
}
}

private fun setupNapi(project: Project, extension: JsCompilationConfiguration) {
project.afterEvaluate {
val compileNapiAll by project.tasks.creating { group = "nativeCompilation" }

with(extension) {
if (baseInputPaths.isEmpty() || outputDir.isEmpty() || targets.isEmpty()) return@afterEvaluate
}

extension.targets.forEach { target ->
project.tasks
.register<NapiCompilationTask>("compileNapi${target.os.capitalize()}${target.arch.capitalize()}", target)
.also { compileNapiAll.dependsOn(it.get()) }
.configure {
for (path in extension.baseInputPaths) inputs.dir(path / target.os)
outputs.dir(extension.outputDir)
}
}

tasks.withType<ProcessResources>().named("jsProcessResources") {
dependsOn(compileNapiAll)
from(project.file(extension.outputDir))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.github.animeshz.keyboard_mouse.native_compile

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.Project
import kotlin.system.exitProcess

internal fun Project.checkDockerInstallation() {
println("Checking docker installation")

val exit = exec {
commandLine(if (Os.isFamily(Os.FAMILY_WINDOWS)) listOf("cmd", "/c", "where", "docker") else listOf("which", "docker"))
isIgnoreExitValue = true
}.exitValue
if (exit != 0) {
println("Please install docker before running this task")
exitProcess(1)
}
}
7 changes: 4 additions & 3 deletions docker/cross-build/linux-x64/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,29 @@ FROM dockcross/linux-x64

LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com"

ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x64
ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-linux-x64
ENV WORK_DIR=/work
ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
ENV X11_HEADERS_DIR=/usr/include/X11/
ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api

RUN \
apt update && \
curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
apt install --no-install-recommends --yes \
curl \
python3 \
nodejs \
npm \
libx11-dev \
libxi-dev \
libxtst-dev && \
npm install -g node-gyp && \
npm install -g cmake-js && \
mkdir -p ${JNI_HEADERS_DIR} && \
cd ${JNI_HEADERS_DIR} && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h && \
mkdir -p $NODE_ADDON_API_HEADERS_DIR && \
cd $NODE_ADDON_API_HEADERS_DIR && \
npm pack node-addon-api@3.1.0 && \
tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \
rm node-addon-api-3.1.0.tgz
Expand Down
16 changes: 8 additions & 8 deletions docker/cross-build/linux-x86/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM dockcross/linux-x86

LABEL maintainer="Animesh Sahu animeshsahu19@yahoo.com"

ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:jni-build-linux-x86
ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-linux-x86
ENV WORK_DIR=/work
ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
ENV X11_HEADERS_DIR=/usr/include/X11/
Expand All @@ -11,22 +11,22 @@ ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}}/support-files/headers/node-addon-api
RUN \
dpkg --add-architecture i386 && \
apt update && \
# curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
apt install --no-install-recommends --yes \
curl \
python3 \
nodejs \
npm \
# nodejs \
libx11-dev:i386 \
libxi-dev:i386 \
libxtst-dev:i386 && \
npm install -g node-gyp && \
# npm install -g cmake-js && \
mkdir -p ${JNI_HEADERS_DIR} && \
cd ${JNI_HEADERS_DIR} && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/unix/native/include/jni_md.h' > jni_md.h && \
mkdir -p $NODE_ADDON_API_HEADERS_DIR && \
npm pack node-addon-api@3.1.0 && \
tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \
rm node-addon-api-3.1.0.tgz
mkdir -p $NODE_ADDON_API_HEADERS_DIR
# npm pack node-addon-api@3.1.0 && \
# tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \
# rm node-addon-api-3.1.0.tgz

WORKDIR ${WORK_DIR}
27 changes: 20 additions & 7 deletions docker/cross-build/windows-x64/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,37 @@ ENV DEFAULT_DOCKCROSS_IMAGE animeshz/keyboard-mouse-kt:cross-build-windows-x64
ENV WORK_DIR=/work
ENV JNI_HEADERS_DIR=${WORK_DIR}/support-files/headers/jni
ENV NODE_ADDON_API_HEADERS_DIR=${WORK_DIR}/support-files/headers/node-addon-api
ENV WINDOWS_NODE_LINK_DIR=${WORK_DIR}/support-files/link/node

COPY keyboard-kt/src/jsMain/cpp/windows/node.def ${WINDOWS_NODE_LINK_DIR}/node.def

RUN \
apt-get update && \
curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
apt-get install -f && \
apt-get install --no-install-recommends --yes \
curl \
python3 \
unzip \
nodejs && \
npm install -g cmake-js && \
# Download and pack JNI headers
mkdir -p ${JNI_HEADERS_DIR} && \
cd ${JNI_HEADERS_DIR} && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' > jni.h && \
curl 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' > jni_md.h && \
mkdir -p $NODE_ADDON_API_HEADERS_DIR && \
cd $NODE_ADDON_API_HEADERS_DIR && \
npm pack node-addon-api@3.1.0 && \
curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/share/native/include/jni.h' && \
curl -LO 'https://raw.githubusercontent.com/openjdk/jdk/master/src/java.base/windows/native/include/jni_md.h' && \
# Download and pack node-addon-api headers
mkdir -p ${NODE_ADDON_API_HEADERS_DIR} && \
cd ${NODE_ADDON_API_HEADERS_DIR} && \
curl -LO 'https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz' && \
tar -xzvf node-addon-api-3.1.0.tgz --strip-components=1 && \
rm node-addon-api-3.1.0.tgz
rm node-addon-api-3.1.0.tgz && \
# Download, build and pack linkable object file
cd ${WINDOWS_NODE_LINK_DIR} && \
curl -LO 'https://nodejs.org/dist/v14.15.4/node-v14.15.4-win-x64.zip' && \
unzip node-v14.15.4-win-x64.zip "node-v14.15.4-win-x64/node.exe" && \
x86_64-w64-mingw32.shared-dlltool -d node.def -y node.a && \
rm node-v14.15.4-win-x64.zip && \
rm -rf node-v14.15.4-win-x64 && \
rm node.def

WORKDIR ${WORK_DIR}
Loading

0 comments on commit a7e637e

Please sign in to comment.