Skip to content

Commit

Permalink
Merge pull request #136 from kornilova-l/sbt-plugin
Browse files Browse the repository at this point in the history
sbt plugin improvements
  • Loading branch information
jonas authored Jul 27, 2018
2 parents 942598b + 0595458 commit efcdf01
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,10 @@ import org.scalanative.bindgen.Bindgen
* Keys are defined in [[ScalaNativeBindgenPlugin.autoImport]].
*
* - `nativeBindgenPath`: Path to the `scala-native-bindgen` executable.
* - `nativeBindgenHeader`: The C header file to read.
*
* - `nativeBindgenPackage`: Package of the enclosing object.
* No package by default.
*
* - `name in nativeBindgen`: Name of the enclosing object.
*
* - `nativeBindings`: Settings for each binding to generate.
* - `target in nativeBindgen`: Output folder of the generated code.
* - `version in nativeBindgen`: Version of the `scala-native-bindgen`
* to use when automatically downloading the executable.
*
* - `nativeBindgenLink`: Name of library to be linked.
*
* - `nativeBindgen`: Generate Scala Native bindings.
*
* @example
Expand All @@ -53,62 +45,65 @@ import org.scalanative.bindgen.Bindgen
object ScalaNativeBindgenPlugin extends AutoPlugin {

object autoImport {
case class NativeBinding(
name: String,
header: File,
packageName: Option[String],
link: Option[String],
excludePrefix: Option[String]
)
val ScalaNativeBindgen = config("scala-native-bindgen").hide
val nativeBindgenPath =
taskKey[File]("Path to the scala-native-bindgen executable")
val nativeBindgenHeader = taskKey[File]("C header file")
val nativeBindgenPackage =
settingKey[Option[String]]("Package for the generated code")
val nativeBindgenLink =
settingKey[Option[String]]("Name of library to be linked")
val nativeBindgenExclude = settingKey[Option[String]]("Exclude prefix")
val nativeBindgen = taskKey[File]("Generate Scala Native bindings")
val nativeBindings =
settingKey[Seq[NativeBinding]]("Configuration for each bindings")
val nativeBindgen = taskKey[Seq[File]]("Generate Scala Native bindings")
}
import autoImport._

override def requires = plugins.JvmPlugin

override def projectSettings: Seq[Setting[_]] =
inConfig(ScalaNativeBindgen)(Defaults.configSettings) ++
nativeBindgenScopedSettings(Compile) ++
Def.settings(
ivyConfigurations += ScalaNativeBindgen,
version in nativeBindgen := BuildInfo.version,
libraryDependencies ++= {
artifactName.map { name =>
val bindgenVersion = (version in nativeBindgen).value
val url =
s"${BuildInfo.projectUrl}/releases/download/v$bindgenVersion/$name"

BuildInfo.organization % name % bindgenVersion % ScalaNativeBindgen from (url)
}.toSeq
},
nativeBindgenPath := {
val scalaNativeBindgenUpdate = (update in ScalaNativeBindgen).value

val artifactFile = artifactName match {
case None =>
sys.error(
"No downloadable binaries available for your OS, " +
"please provide path via `nativeBindgenPath`")
case Some(name) =>
scalaNativeBindgenUpdate
.select(artifact = artifactFilter(name = name))
.head
}

// Set the executable bit on the expected path to fail if it doesn't exist
for (view <- Option(
Files.getFileAttributeView(artifactFile.toPath,
classOf[PosixFileAttributeView]))) {
val permissions = view.readAttributes.permissions
if (permissions.add(PosixFilePermission.OWNER_EXECUTE))
view.setPermissions(permissions)
nativeBindgenScopedSettings(Compile) ++
Def.settings(
ivyConfigurations += ScalaNativeBindgen,
version in nativeBindgen := BuildInfo.version,
libraryDependencies ++= {
artifactName.map { name =>
val bindgenVersion = (version in nativeBindgen).value
val url =
s"${BuildInfo.projectUrl}/releases/download/v$bindgenVersion/$name"

BuildInfo.organization % name % bindgenVersion % ScalaNativeBindgen from (url)
}.toSeq
},
nativeBindgenPath := {
val scalaNativeBindgenUpdate = (update in ScalaNativeBindgen).value

val artifactFile = artifactName match {
case None =>
sys.error(
"No downloadable binaries available for your OS, " +
"please provide path via `nativeBindgenPath`")
case Some(name) =>
scalaNativeBindgenUpdate
.select(artifact = artifactFilter(name = name))
.head
}

// Set the executable bit on the expected path to fail if it doesn't exist
for (view <- Option(
Files.getFileAttributeView(artifactFile.toPath,
classOf[PosixFileAttributeView]))) {
val permissions = view.readAttributes.permissions
if (permissions.add(PosixFilePermission.OWNER_EXECUTE))
view.setPermissions(permissions)
}

artifactFile
}

artifactFile
}
)
)

private implicit class BindgenOps(val bindgen: Bindgen) extends AnyVal {
def maybe[T](opt: Option[T], f: Bindgen => T => Bindgen): Bindgen =
Expand All @@ -127,27 +122,30 @@ object ScalaNativeBindgenPlugin extends AutoPlugin {
def nativeBindgenScopedSettings(conf: Configuration): Seq[Setting[_]] =
inConfig(conf)(
Def.settings(
nativeBindgenHeader := {
sys.error("nativeBindgenHeader not configured")
},
nativeBindgenPackage := None,
nativeBindgenExclude := None,
sourceGenerators += Def.task { Seq(nativeBindgen.value) },
name in nativeBindgen := "ScalaNativeBindgen",
nativeBindings := Seq.empty,
sourceGenerators += Def.task { nativeBindgen.value },
target in nativeBindgen := sourceManaged.value / "sbt-scala-native-bindgen",
nativeBindgen := {
val output = sourceManaged.value / "sbt-scala-native-bindgen" / "ScalaNativeBindgen.scala"

Bindgen()
.bindgenExecutable(nativeBindgenPath.value)
.header(nativeBindgenHeader.value)
.name((name in nativeBindgen).value)
.maybe(nativeBindgenLink.value, _.link)
.maybe(nativeBindgenPackage.value, _.packageName)
.maybe(nativeBindgenExclude.value, _.excludePrefix)
.generate()
.writeToFile(output)

output
val bindgenPath = nativeBindgenPath.value
val bindings = nativeBindings.value
val outputDirectory = (target in nativeBindgen).value

bindings.map {
binding =>
val output = outputDirectory / s"${binding.name}.scala"

Bindgen()
.bindgenExecutable(bindgenPath)
.header(binding.header)
.name(binding.name)
.maybe(binding.link, _.link)
.maybe(binding.packageName, _.packageName)
.maybe(binding.excludePrefix, _.excludePrefix)
.generate()
.writeToFile(output)

output
}
}
))
}
86 changes: 70 additions & 16 deletions sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,84 @@ scalaVersion := "2.11.12"
inConfig(Compile)(
Def.settings(
nativeBindgenPath := file(System.getProperty("bindgen.path")),
nativeBindgenHeader := (resourceDirectory in Compile).value / "header.h",
nativeBindgenPackage := Some("org.example.app"),
nativeBindgenLink := Some("app"),
nativeBindgenExclude := Some("__"),
name in nativeBindgen := "AppAPI"
nativeBindings := Seq(
NativeBinding(
name = "stdlib",
header = (resourceDirectory in Compile).value / "stdlib.h",
packageName = Some("org.example.app.stdlib"),
link = None,
excludePrefix = Some("__")
)
)
))

TaskKey[Unit]("check") := {
val file = (nativeBindgen in Compile).value
val expected =
"""package org.example.app
val nativeBindgenCustomTarget = SettingKey[File]("nativeBindgenCustomTarget")
nativeBindgenCustomTarget := baseDirectory.value / "src/main/scala/org/example"

val nativeBindgenCoreBinding =
SettingKey[NativeBinding]("nativeBindgenCoreBinding")
nativeBindgenCoreBinding := {
NativeBinding(
name = "core",
header = (resourceDirectory in Compile).value / "core.h",
packageName = Some("org.example.app.core"),
link = Some("core"),
excludePrefix = None
)
}

val StdlibOutput =
"""package org.example.app.stdlib
|
|import scala.scalanative._
|import scala.scalanative.native._
|
|@native.extern
|object stdlib {
| def access(path: native.CString, mode: native.CInt): native.CInt = native.extern
| def read(fildes: native.CInt, buf: native.Ptr[Byte], nbyte: native.CInt): native.CInt = native.extern
| def printf(format: native.CString, varArgs: native.CVararg*): native.CInt = native.extern
|}
""".stripMargin

def assertFileContent(file: File, expected: String): Unit = {
val actual = IO.read(file).trim
if (actual != expected.trim) {
println(s"== [ actual ${file.getName} ] ========")
println(actual)
println(s"== [ expected ${file.getName} ] ========")
println(expected.trim)
}
assert(actual == expected.trim)
}

TaskKey[Unit]("checkSingle") := {
val files = (nativeBindgen in Compile).value
assert(files.forall(_.exists()))
assert(files.length == 1)
assertFileContent(files.head, StdlibOutput)
}

TaskKey[Unit]("checkMultiple") := {
val files = (nativeBindgen in Compile).value
assert(files.forall(_.exists()))
assert(files.length == 2)

assertFileContent(files(0), StdlibOutput)

val CoreOutput =
"""package org.example.app.core
|
|import scala.scalanative._
|import scala.scalanative.native._
|
|@native.link("app")
|@native.link("core")
|@native.extern
|object AppAPI {
| def access(path: native.CString, mode: native.CInt): native.CInt = native.extern
| def read(fildes: native.CInt, buf: native.Ptr[Byte], nbyte: native.CInt): native.CInt = native.extern
| def printf(format: native.CString, varArgs: native.CVararg*): native.CInt = native.extern
|object core {
| def count_words(text: native.CString): native.CInt = native.extern
| def __not_excluded(): Unit = native.extern
|}
""".stripMargin

assert(file.exists)
assert(IO.read(file).trim == expected.trim)
assertFileContent(files(1), CoreOutput)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
int count_words(const char *text);
void __not_excluded(void);
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
int access(const char *path, int mode);
int read(int fildes, void *buf, int nbyte);
int printf(const char *restrict format, ...);
int __excluded(void);
11 changes: 10 additions & 1 deletion sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
> check
> checkSingle
$ exists target/scala-2.11/src_managed/main/sbt-scala-native-bindgen/stdlib.scala
> set target in (Compile, nativeBindgen) := nativeBindgenCustomTarget.value
> checkSingle
$ exists src/main/scala/org/example/stdlib.scala
> clean
> set nativeBindings in Compile += nativeBindgenCoreBinding.value
> checkMultiple
$ exists src/main/scala/org/example/core.scala
$ exists src/main/scala/org/example/stdlib.scala

0 comments on commit efcdf01

Please sign in to comment.