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

Add support for Cargo (Rust) #42

Merged
merged 15 commits into from
Jul 26, 2021
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Scala 3 support [#45](https://github.com/sbt/sbt-jni/issues/45)
- Add CMake support of versions < 3.15 [#51](https://github.com/sbt/sbt-jni/pull/51)
- Add support for Rust/Cargo [#42](https://github.com/sbt/sbt-jni/pull/42)

### Changed
- Upgrade gjavah [#43](https://github.com/sbt/sbt-jni/pull/43)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ JniNative adds the capability of building native code (compiling and linking) to
Since this plugin is basically a command-line wrapper, native build tools must follow certain calling conventions to be compatible. The supported build tools are currently:

- CMake
- Cargo (Rust)

An initial, compatible build template can be obtained by running `sbt nativeInit <tool>`. Once the native build tool initialised, projects are built by calling the `sbt nativeCompile` task.

Expand Down
11 changes: 11 additions & 0 deletions plugin/src/main/resources/ch/jodersky/sbt/jni/templates/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "{{project}}"
version = "0.1.0"
authors = ["John Doe <[email protected]>"]
edition = "2018"

[dependencies]
jni = "0.19"

[lib]
crate_type = ["cdylib"]
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ trait BuildTool {

baseDirectory.mkdir()
val out = baseDirectory.toPath().resolve(name)
Files.createDirectories(out.getParent)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is necessary, so that the possibly deeply nested structure of directories is created before trying creating a new file.

Files.write(out, replaced.getBytes)
out.toFile()
}
Expand Down Expand Up @@ -77,3 +78,10 @@ trait BuildTool {
def getInstance(baseDirectory: File, buildDirectory: File, logger: Logger): Instance

}

object BuildTool {
lazy val buildTools: Map[String, BuildTool] = Map(
CMake.name.toLowerCase -> CMake,
Cargo.release.name.toLowerCase -> Cargo.release
Copy link
Member

Choose a reason for hiding this comment

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

hmm, what do you about making the releaseFlag configurable? i.e. having a cargoReleaseProfile setting? It can still be useful for debugging purposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is!

nativeBuildTool := Cargo.make(false)

Copy link
Member

Choose a reason for hiding this comment

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

@sideeffffect 💯 niice!

)
}
77 changes: 77 additions & 0 deletions plugin/src/main/scala/ch/jodersky/sbt/jni/build/Cargo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package ch.jodersky.sbt.jni.build

import sbt._

import java.io.File
import scala.sys.process._

class Cargo(protected val release: Boolean = true) extends BuildTool {

def name: String = "Cargo"

def detect(baseDirectory: File): Boolean =
baseDirectory.list().contains("Cargo.toml")

protected def templateMappings: List[(String, String)] = List(
"/ch/jodersky/sbt/jni/templates/Cargo.toml" -> "Cargo.toml"
)

def getInstance(baseDirectory: File, buildDirectory: File, logger: sbt.Logger): Instance =
new Instance(baseDirectory, logger)

class Instance(protected val baseDirectory: File, protected val logger: sbt.Logger) extends super.Instance {
// IntelliJ friendly logger, IntelliJ doesn't start tests if a line is printed as "error", which Cargo does for regular output
protected val log: ProcessLogger = new ProcessLogger {
def out(s: => String): Unit = logger.info(s)
def err(s: => String): Unit = logger.warn(s)
def buffer[T](f: => T): T = f
}

def clean(): Unit =
Process("cargo clean", baseDirectory) ! log

def library(targetDirectory: File): File = {
val releaseFlag = if (release) "--release " else ""
val ev =
Process(
s"cargo build $releaseFlag--target-dir ${targetDirectory.getAbsolutePath}",
baseDirectory
) ! log
if (ev != 0) sys.error(s"Building native library failed. Exit code: $ev")

val subdir = if (release) "release" else "debug"
val products: List[File] =
(targetDirectory / subdir * ("*.so" | "*.dylib")).get.filter(_.isFile).toList

// only one produced library is expected
products match {
case Nil =>
sys.error(
s"No files were created during compilation, " +
s"something went wrong with the $name configuration."
)
case head :: Nil =>
head
case head :: _ =>
logger.warn(
"More than one file was created during compilation, " +
s"only the first one (${head.getAbsolutePath}) will be used."
)
head
}
}
}
}

object Cargo {

/**
* If `release` is `true`, `cargo build` will run with the `--release` flag.
*/
def make(release: Boolean = true): BuildTool = new Cargo(release)

/**
* Cargo build tool, with the `--release` flag.
*/
lazy val release: BuildTool = make()
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object JniNative extends AutoPlugin {
nativeCompile / sourceDirectory := sourceDirectory.value / "native",
nativeCompile / target := target.value / "native" / nativePlatform.value,
nativeBuildTool := {
val tools = Seq(CMake)
val tools = BuildTool.buildTools.values.toList

val src = (nativeCompile / sourceDirectory).value

Expand Down Expand Up @@ -126,10 +126,10 @@ object JniNative extends AutoPlugin {

val log = streams.value.log

def getTool(toolName: String): BuildTool = toolName.toLowerCase match {
case "cmake" => CMake
case _ => sys.error("Unsupported build tool: " + toolName)
}
def getTool(toolName: String): BuildTool = BuildTool.buildTools.getOrElse(
toolName.toLowerCase,
sys.error("Unsupported build tool: " + toolName)
)

val args = spaceDelimited("<tool> [<libname>]").parsed.toList

Expand Down
1 change: 1 addition & 0 deletions plugin/src/sbt-test/sbt-jni/simple-cargo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Very basic Rust/Cargo test.
11 changes: 11 additions & 0 deletions plugin/src/sbt-test/sbt-jni/simple-cargo/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ivyLoggingLevel := UpdateLogging.Quiet

lazy val root = (project in file(".")).aggregate(core, native)

lazy val core = project
.settings(libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.9" % Test)
.dependsOn(native % Runtime)

lazy val native = project
.settings(nativeCompile / sourceDirectory := baseDirectory.value) // `baseDirectory`, not `sourceDirectory`
Copy link
Member

@pomadchin pomadchin Jul 26, 2021

Choose a reason for hiding this comment

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

Ha, what a trick, indeed, that's due to the Cargo project structure (a comment for myself):

├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs

I'll add it into docs later.

.enablePlugins(JniNative)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package simplecargo

import ch.jodersky.jni.nativeLoader

@nativeLoader("adder")
class Adder(val base: Int) {
@native def plus(term: Int): Int // implemented in libadder.so
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package simplecargo

object Main {

def main(args: Array[String]): Unit = {
println("hello")
val adder = new Adder(1)
val sum = adder.plus(2)
println(s"sum: $sum")
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package simplecargo

import org.scalatest.flatspec._

class SimpleSpec extends AnyFlatSpec {

"Calling native methods in tests" should "work" in {
assert(new Adder(12).plus(34) == 46)
}

}
24 changes: 24 additions & 0 deletions plugin/src/sbt-test/sbt-jni/simple-cargo/native/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This is the interface to the JVM that we'll call the majority of our
// methods on.
use jni::JNIEnv;

// These objects are what you should use as arguments to your native
// function. They carry extra lifetime information to prevent them escaping
// this context and getting used after being GC'd.
use jni::objects::JObject;

use jni::sys::jint;

// This keeps Rust from "mangling" the name and making it unique for this
// crate.
#[no_mangle]
pub extern "system" fn Java_simplecargo_Adder_plus(
env: JNIEnv,
object: JObject,
term: jint,
) -> jint {
let base = env.get_field(object, "base", "I").unwrap().i().unwrap();
println!("Printing from rust library. base: {}", base);
println!("Printing from rust library. term: {}", term);
base + term
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import sbt._
import sbt.Keys._

object ScriptedHelper extends AutoPlugin {

override def requires = empty
override def trigger = allRequirements

override def projectSettings = Seq(
scalacOptions ++= Seq("-feature", "-deprecation"),
crossScalaVersions := Seq("2.13.6", "2.12.14"),
scalaVersion := crossScalaVersions.value.head
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.5.5
3 changes: 3 additions & 0 deletions plugin/src/sbt-test/sbt-jni/simple-cargo/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ivyLoggingLevel := UpdateLogging.Quiet

addSbtPlugin("com.github.sbt" % "sbt-jni" % System.getProperty("plugin.version"))
3 changes: 3 additions & 0 deletions plugin/src/sbt-test/sbt-jni/simple-cargo/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> nativeInit cargo adder
> +test
> +core/run