Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build JavaCPP libraries at compile time #20

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist: trusty
sudo: false
language: scala
jdk:
Expand Down
2 changes: 0 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ publishArtifact in Test := false

pomIncludeRepository := { _ => false }

libraryDependencies += "org.bytedeco" % "javacpp" % "1.4.3"

scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-Xlint", "-Xlog-free-terms")

publishTo := {
Expand Down
140 changes: 140 additions & 0 deletions src/main/scala/org/bytedeco/sbt/javacpp/JavaCppPlugin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.bytedeco.sbt.javacpp

import java.net.URLClassLoader
import java.util.Properties

import sbt.Keys._
import sbt._

import scala.language.{ postfixOps, reflectiveCalls }
import scala.util.Try

object JavaCppPlugin extends AutoPlugin {
Copy link
Member

Choose a reason for hiding this comment

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

Why did you create a new file?

Copy link
Author

Choose a reason for hiding this comment

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

This is not required. But when there is another Plugin object, which I'd like to enable/disable, I would not have to specify the package.


override def projectSettings: Seq[Setting[_]] = {
import autoImport._
Seq(
autoCompilerPlugins := true,
javaCppClasses := Seq.empty,
javaCppCustomizer := identity,
javaCppPlatform := Platform.current,
javaCppPresetLibs := Seq.empty,
javaCppVersion := Versions.javaCppVersion,
libraryDependencies += {
"org.bytedeco" % "javacpp" % javaCppVersion.value jar
},
javaCppPresetDependencies,
javaCppBuild := javaCpp.value,
products in Compile := (products in Compile).dependsOn(javaCppBuild).value)
}

object Versions {
val javaCppVersion = "1.4.3"
}

object autoImport {
type JavaCppBuilder = {
Copy link
Member

Choose a reason for hiding this comment

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

If the real org.bytedeco.javacpp.tools.Builder is updated/changed (e.g. when the user configures a newer version), would this still work?

Copy link
Author

Choose a reason for hiding this comment

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

It should work as long as the corresponding methods that are actually invoked are not missing in the newer version of org.bytedeco.javacpp.tools.Builder.

def classPaths(classPath: String): this.type
def classPaths(classPath: Array[String]): this.type
def encoding(encoding: String): this.type
def outputDirectory(outputDirectory: String): this.type
def outputDirectory(outputDirectory: File): this.type
def clean(clean: Boolean): this.type
def generate(generate: Boolean): this.type
def compile(compile: Boolean): this.type
def deleteJniFiles(deleteJniFiles: Boolean): this.type
def header(header: Boolean): this.type
def copyLibs(copyLibs: Boolean): this.type
def copyResources(copyResources: Boolean): this.type
def outputName(outputName: String): this.type
def jarPrefix(jarPrefix: String): this.type
def properties(platform: String): this.type
def properties(properties: Properties): this.type
def propertyFile(propertyFile: String): this.type
def propertyFile(propertyFile: File): this.type
def property(keyValue: String): this.type
def property(key: String, value: String): this.type
def classesOrPackages(classes: Array[String]): this.type
def buildCommand(buildCommand: Array[String]): this.type
def workingDirectory(workingDirectory: String): this.type
def workingDirectory(workingDirectory: File): this.type
def environmentVariables(environmentVariables: java.util.Map[String, String]): this.type
def compilerOptions(options: Array[String]): this.type
def build(): Array[File]
def printHelp(): Unit
def getClass(): Class[_]
}

val javaCppBuild = TaskKey[Seq[File]]("javaCppBuild", "Build Java CPP products")
val javaCppClasses = SettingKey[Seq[String]]("javaCppClasses", "A list of Java CPP classes. Suffix of '.*' ('.**') can be used to match all classes under the specified package (and any subpackages)")
val javaCppCustomizer = SettingKey[JavaCppBuilder => JavaCppBuilder]("javaCppCustomization", "Customize the Java CPP builder")
val javaCppPlatform = SettingKey[Seq[String]]("javaCppPlatform", """The platform that you want to compile for (defaults to the platform of the current computer). You can also set this via the "sbt.javacpp.platform" System Property """)
val javaCppPresetLibs = SettingKey[Seq[(String, String)]]("javaCppPresetLibs", "List of additional JavaCPP presets that you would wish to bind lazily, defaults to an empty list")
val javaCppVersion = SettingKey[String]("javaCppVersion", s"Version of Java CPP that you want to use, defaults to ${Versions.javaCppVersion}")
}

override def requires: Plugins = plugins.JvmPlugin

override def trigger: PluginTrigger = allRequirements

private def javaCppPresetDependencies: Def.Setting[Seq[ModuleID]] = {
import autoImport._
libraryDependencies ++= {
lazy val cppPresetVersion = buildPresetVersion(javaCppVersion.value)
javaCppPresetLibs.value.flatMap {
case (libName, libVersion) =>
val generic = "org.bytedeco.javacpp-presets" % libName % s"$libVersion-$cppPresetVersion" classifier ""
val platformSpecific = javaCppPlatform.value.map { platform =>
"org.bytedeco.javacpp-presets" % libName % s"$libVersion-$cppPresetVersion" classifier platform
}
generic +: platformSpecific
}
}
}

/**
* Before javacpp 1.4
* Given a version string, simply drops the patch level and returns the major-minor version only
*
* Starting from javacpp 1.4
* The version number of the presets are equal to the javacpp version.
*
* @param version eg. "1.4.2"
*/
private def buildPresetVersion(version: String): String =
version match {
case VersionSplit(a :: b :: _) if a < 2 & b < 4 => s"$a.$b"
case VersionSplit(_) => version
case _ => throw new IllegalArgumentException("Version format not recognized")
}

private object VersionSplit {
def unapply(arg: String): Option[List[Int]] =
Try(arg.split('.').map(_.toInt).toList).toOption
}

private def javaCpp = Def.task {
import autoImport._
val classes = javaCppClasses.value
val customizer = javaCppCustomizer.value
val dependencies = (dependencyClasspath in Compile).value
val output = (classDirectory in Compile).value
val _ = (compile in Compile).value

val cl = new URLClassLoader(dependencies.map(_.data.toURI.toURL).toArray, null)
val thread = Thread.currentThread()
thread.setContextClassLoader(cl)
val builder = cl.loadClass("org.bytedeco.javacpp.tools.Builder").newInstance().asInstanceOf[JavaCppBuilder]
val saved = thread.getContextClassLoader

try {
customizer(
builder
.classPaths(output.getAbsolutePath)
.classPaths(dependencies.map(_.data.absolutePath).toArray)
.classesOrPackages(classes.toArray)).build()
} finally {
thread.setContextClassLoader(saved)
}
}
}
30 changes: 24 additions & 6 deletions src/main/scala/org/bytedeco/sbt/javacpp/Platform.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package org.bytedeco.sbt.javacpp

import org.bytedeco.javacpp.Loader

/**
* Created by Lloyd on 2/22/16.
*/

object Platform {

private val platformOverridePropertyKey: String = "sbt.javacpp.platform"
Expand All @@ -20,7 +17,28 @@ object Platform {
*/
val current: Seq[String] = sys.props.get(platformOverridePropertyKey) match {
case Some(platform) if platform.trim().nonEmpty => platform.split(' ')
case _ => Seq(Loader.getPlatform)
case _ =>
Copy link
Member

Choose a reason for hiding this comment

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

What's the advantage of not pulling in JavaCpp at the sbt level and re-using the loader?

Copy link
Author

Choose a reason for hiding this comment

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

I chose this style, so I didn't have to think about whether the potential difference of the libraries would cause any trouble. It should work either way, though.

val jvmName = System.getProperty("java.vm.name", "").toLowerCase
var osName = System.getProperty("os.name", "").toLowerCase
var osArch = System.getProperty("os.arch", "").toLowerCase
val abiType = System.getProperty("sun.arch.abi", "").toLowerCase
val libPath = System.getProperty("sun.boot.library.path", "").toLowerCase
if (jvmName.startsWith("dalvik") && osName.startsWith("linux")) {
osName = "android"
} else if (jvmName.startsWith("robovm") && osName.startsWith("darwin")) {
osName = "ios"
osArch = "arm"
} else if (osName.startsWith("mac os x") || osName.startsWith("darwin")) {
osName = "macosx"
} else {
val spaceIndex = osName.indexOf(' ')
if (spaceIndex > 0) osName = osName.substring(0, spaceIndex)
}
if (osArch == "i386" || osArch == "i486" || osArch == "i586" || osArch == "i686") osArch = "x86"
else if (osArch == "amd64" || osArch == "x86-64" || osArch == "x64") osArch = "x86_64"
else if (osArch.startsWith("aarch64") || osArch.startsWith("armv8") || osArch.startsWith("arm64")) osArch = "arm64"
else if (osArch.startsWith("arm") && ((abiType == "gnueabihf") || libPath.contains("openjdk-armhf"))) osArch = "armhf"
else if (osArch.startsWith("arm")) osArch = "arm"
Seq(osName + "-" + osArch)
}

}
}
74 changes: 0 additions & 74 deletions src/main/scala/org/bytedeco/sbt/javacpp/Plugin.scala

This file was deleted.

8 changes: 7 additions & 1 deletion src/sbt-test/sbt-javacpp/simple/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ version := "0.1"

scalaVersion := "2.11.12"

libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
javaCppClasses := Seq("javacpp.*")

javaCppVersion := "1.5.1"

javaCppCustomizer := { builder => builder.compilerOptions(Array("-std=c++11")) }

libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package javacpp;

import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;

@Platform(include="<vector>")
@Name("std::vector<std::vector<void*> >")
public class PointerVectorVector extends Pointer {
static { Loader.load(); }
public PointerVectorVector() { allocate(); }
public PointerVectorVector(long n) { allocate(n); }
public PointerVectorVector(Pointer p) { super(p); } // this = (vector<vector<void*> >*)p
private native void allocate(); // this = new vector<vector<void*> >()
private native void allocate(long n); // this = new vector<vector<void*> >(n)
@Name("operator=")
public native @ByRef PointerVectorVector put(@ByRef PointerVectorVector x);

@Name("operator[]")
public native @StdVector PointerPointer get(long n);
public native @StdVector PointerPointer at(long n);

public native long size();
public native @Cast("bool") boolean empty();
public native void resize(long n);
public native @Index long size(long i); // return (*this)[i].size()
public native @Index @Cast("bool") boolean empty(long i); // return (*this)[i].empty()
public native @Index void resize(long i, long n); // (*this)[i].resize(n)

public native @Index Pointer get(long i, long j); // return (*this)[i][j]
public native void put(long i, long j, Pointer p); // (*this)[i][j] = p
}
3 changes: 0 additions & 3 deletions src/sbt-test/sbt-javacpp/simple/src/main/scala/Main.scala

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package javacpp;

import org.scalatest._
import org.bytedeco.javacpp._

class VectorTest extends FlatSpec with BeforeAndAfterAll {

it should "test PointerVectorVector" in {
val v = new PointerVectorVector(13)
v.resize(0, 42) // v[0].resize(42)
val p = new Pointer() {
address = 0xDEADBEEFL
}
v.put(0, 0, p) // v[0][0] = p
assert(!v.empty)
}
}