Skip to content

Commit

Permalink
Merge pull request #8 from alexarchambault/static-jni
Browse files Browse the repository at this point in the history
Generate and publish a .lib file
  • Loading branch information
alexarchambault authored Sep 7, 2021
2 parents 7dbaf06 + c08881d commit 33ef1d3
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- name: Test
if: runner.os == 'Windows'
run: ./mill -i __.test
- name: Publish local
if: runner.os == 'Windows'
run: ./mill -i __.publishLocal

publish:
needs: test
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# coursier-jni-utils

*coursier-jni-utils* is a small library allowing to tap into various Windows
native API from Java / Scala via JNI.


## Developer docs

*coursier-jni-utils* is built with
[Mill](https://com-lihaoyi.github.io/mill). It comes upwith Mill launchers
(`mill`, `mill.bat`), so that only a JVM and [mingw](https://www.mingw-w64.org)
should be needed to build it.

### Requirements

#### JVM

A JVM, such as AdoptOpenJDK 8 or 11, is required.
`java -version` should work fine, and print a version
higher than or equal to `8`.

#### mingw

[mingw](https://www.mingw-w64.org) is required to build the dll files out of
`.h` / `.c` files calling native Windows native APIs, and interfacing with JNI.

On Windows, the *coursier-jni-utils* build assumes mingw is installed under
`C:\msys64`. If it's installed at a different location, edit the `msys2Entrypoint`
method in `deps.sc` accordingly.

On Linux and macOS, the *coursier-jni-utils* build mingw gcc can be run with
`x86_64-w64-mingw32-gcc`.

### IDE

[IntelliJ IDEA](https://www.jetbrains.com/idea) is the recommended IDE to
develop on jni-utils. Prior to opening the project with IDEA, run
```text
$ ./mill mill.scalalib.GenIdea/idea
```

Then open the `jni-utils` directory in IDEA.

### Compiling

```text
$ ./mill __.compile
```

This should automatically compile `.h` / `.c` files with mingw, create a
corresponding `.dll`, and make it available as a resource from Java.

### Running tests

Some simple tests can be run with
```text
$ ./mill __.test
```
6 changes: 5 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import mill._, scalalib._
import scala.concurrent.duration._


object `windows-jni-utils` extends WindowsUtils with HasCSources with JniUtilsPublishModule with WithDllNameJava {
object `windows-jni-utils` extends MavenModule with JniUtilsPublishVersion with HasCSources with JniUtilsPublishModule with WithDllNameJava {
def linkingLibs = Seq("ole32")

def compile = T{
Expand All @@ -22,6 +22,10 @@ object `windows-jni-utils` extends WindowsUtils with HasCSources with JniUtilsPu
def windowsJavaHome = sharedWindowsJavaHome()
}

object `windows-jni-utils-graalvm` extends WindowsUtils with JniUtilsPublishModule {
def moduleDeps = Seq(`windows-jni-utils`)
}

object `windows-jni-utils-bootstrap` extends WindowsUtils with JniUtilsPublishModule {
def moduleDeps = Seq(`windows-jni-utils`)
}
Expand Down
Binary file added csjniutils-0.2.5.lib
Binary file not shown.
80 changes: 74 additions & 6 deletions settings.sc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import $ivy.`org.codehaus.plexus:plexus-archiver:4.2.2`

import de.tobiasroeser.mill.vcs.version.VcsVersion
import mill._, scalalib._
import mill.scalalib.publish.PublishInfo
import org.codehaus.plexus.archiver.zip.ZipUnArchiver

import scala.concurrent.duration.Duration
Expand Down Expand Up @@ -81,7 +82,7 @@ lazy val isWindows = System.getProperty("os.name")
.toLowerCase(java.util.Locale.ROOT)
.contains("windows")

trait HasCSources extends JavaModule {
trait HasCSources extends JavaModule with PublishModule {

def windowsJavaHome: T[String]
def dllName: T[String]
Expand Down Expand Up @@ -129,13 +130,37 @@ trait HasCSources extends JavaModule {
PathRef(output.resolveFrom(os.pwd))
}
}
def dll = T.persistent {

private def vcVersions = Seq("2019", "2017")
private def vcEditions = Seq("Enterprise", "Community", "BuildTools")
private lazy val vcvarsCandidates = Option(System.getenv("VCVARSALL")) ++ {
for {
version <- vcVersions
edition <- vcEditions
} yield """C:\Program Files (x86)\Microsoft Visual Studio\""" + version + "\\" + edition + """\VC\Auxiliary\Build\vcvars64.bat"""
}

private def vcvarsOpt: Option[os.Path] =
vcvarsCandidates
.iterator
.map(os.Path(_, os.pwd))
.filter(os.exists(_))
.toStream
.headOption

def dllAndDef = T.persistent {

// about the .def, we build it in order to build a .lib,
// see https://stackoverflow.com/a/3031167/3714539

val dllName0 = dllName()
val destDir = T.ctx().dest / "dlls"
if (!os.exists(destDir))
os.makeDir.all(destDir)
val dest = destDir / s"$dllName0.dll"
val defDest = destDir / s"$dllName0.def"
val relDest = dest.relativeTo(os.pwd)
val relDefDest = defDest.relativeTo(os.pwd)
val objs = cCompile()
val q = "\""
val objsArgs = objs.map(o => o.path.relativeTo(os.pwd).toString).distinct
Expand All @@ -146,24 +171,67 @@ trait HasCSources extends JavaModule {
}
if (needsUpdate) {
val command =
if (isWindows) Seq(s"${gcc.mkString(" ")} -s -shared -o $q$relDest$q ${objsArgs.map(o => q + o + q).mkString(" ")} -municode ${libsArgs.map(l => q + l + q).mkString(" ")}")
else gcc ++ Seq("-s", "-shared", "-o", relDest.toString) ++ objsArgs ++ Seq("-municode") ++ libsArgs
if (isWindows) Seq(s"${gcc.mkString(" ")} -s -shared -o $q$relDest$q ${objsArgs.map(o => q + o + q).mkString(" ")} -municode ${libsArgs.map(l => q + l + q).mkString(" ")} -Wl,--output-def,$relDefDest")
else gcc ++ Seq("-s", "-shared", "-o", relDest.toString) ++ objsArgs ++ Seq("-municode") ++ libsArgs ++ Seq(s"-Wl,--output-def,$relDefDest")
System.err.println(s"Running ${command.mkString(" ")}")
val res = os
.proc((msysShell ++ command).map(x => x: os.Shellable): _*)
.call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit)
if (res.exitCode != 0)
sys.error(s"${gcc.mkString(" ")} command exited with code ${res.exitCode}")
}
PathRef(dest)
(PathRef(dest), PathRef(defDest))
}
def resources = T.sources {
val dll0 = dll().path
val dll0 = dllAndDef()._1.path
val dir = T.ctx().dest / "dll-resources"
val dllDir = dir / "META-INF" / "native" / "windows64"
os.copy(dll0, dllDir / dll0.last, replaceExisting = true, createFolders = true)
super.resources() ++ Seq(PathRef(dir))
}

def libFile = T {
val defFile = dllAndDef()._2.path
val vcvars = vcvarsOpt.getOrElse {
sys.error("vcvars64.bat not found. Ensure Visual Studio is installed, or put the vcvars64.bat path in VCVARSALL.")
}
val script =
s"""@call "$vcvars"
|if %errorlevel% neq 0 exit /b %errorlevel%
|lib "/def:$defFile"
|""".stripMargin
val scriptPath = T.dest / "run-lib.bat"
os.write.over(scriptPath, script.getBytes, createFolders = true)
os.proc(scriptPath).call(cwd = T.dest)
val libFile = T.dest / (defFile.last.stripSuffix(".def") + ".lib")
if (!os.isFile(libFile))
sys.error(s"Error: $libFile not created")
PathRef(libFile)
}

def extraPublish = super.extraPublish() ++ Seq(
PublishInfo(
file = dllAndDef()._1,
ivyConfig = "compile",
classifier = Some("x86_64-pc-win32"),
ext = "dll",
ivyType = "dll"
),
PublishInfo(
file = dllAndDef()._2,
ivyConfig = "compile",
classifier = Some("x86_64-pc-win32"),
ext = "def",
ivyType = "def"
),
PublishInfo(
file = libFile(),
ivyConfig = "compile",
classifier = Some("x86_64-pc-win32"),
ext = "lib",
ivyType = "lib"
)
)
}

def downloadWindowsJvmArchive(windowsJvmUrl: String, windowsJvmArchiveName: String)(implicit ctx: mill.util.Ctx.Dest) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ static File fromResources(ClassLoader cl) throws IOException, URISyntaxException
}
}

public static void assumeInitialized() {
assumeInitialized(true);
}
public static void assumeInitialized(boolean initialized) {
LoadWindowsLibrary.initialized = initialized;
}

final static Object lock = new Object();
static boolean initialized = false;
public static void ensureInitialized() {
Expand Down

0 comments on commit 33ef1d3

Please sign in to comment.