diff --git a/build.sbt b/build.sbt index ee03fc6..2acde02 100644 --- a/build.sbt +++ b/build.sbt @@ -1,12 +1,19 @@ -import scala.scalanative.sbtplugin.ScalaNativePluginInternal.nativeWorkdir import scala.sys.process._ -import java.nio.file.Path + +addCommandAlias("verify", "; ^test:compile ; ^test ; ^scripted ; docs/makeSite") + +val Versions = new { + val scala210 = "2.10.6" + val scala211 = "2.11.12" + val scala212 = "2.12.6" + val sbt013 = "0.13.17" + val sbt1 = "1.1.6" +} inThisBuild( Def.settings( organization := "org.scalanative.bindgen", version := "0.2-SNAPSHOT", - scalaVersion := "2.11.12", scalacOptions ++= Seq( "-deprecation", "-unchecked", @@ -20,10 +27,18 @@ inThisBuild( git.remoteRepo := scmInfo.value.get.connection.replace("scm:git:", "") )) -val tests = project - .in(file("tests")) +val root = project("scala-native-bindgen") + .in(file(".")) + .aggregate( + tests, + samples, + tools, + sbtPlugin, + docs + ) + +lazy val tests = project("tests") .dependsOn(tools) - .aggregate(samples) .settings( fork in Test := true, javaOptions in Test += { @@ -38,10 +53,11 @@ val tests = project libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test ) -lazy val samples = project +lazy val samples = project("samples") .in(file("tests/samples")) .enablePlugins(ScalaNativePlugin) .settings( + scalaVersion := Versions.scala211, libraryDependencies += "com.lihaoyi" %%% "utest" % "0.6.3" % "test", testFrameworks += new TestFramework("utest.runner.Framework"), nativeLinkStubs := true, @@ -91,19 +107,45 @@ lazy val samples = project } ) -lazy val tools = project in file("tools") +lazy val tools = project("tools") + +lazy val sbtPlugin = project("sbt-scala-native-bindgen", ScriptedPlugin) + .dependsOn(tools) + .settings( + Keys.sbtPlugin := true, + scriptedLaunchOpts += s"-Dplugin.version=${version.value}", + scriptedLaunchOpts += { + val rootDir = (ThisBuild / baseDirectory).value + s"-Dbindgen.path=$rootDir/bindgen/target/scala-native-bindgen" + }, + publishLocal := publishLocal.dependsOn(tools / publishLocal).value + ) -lazy val docs = project - .in(file("docs")) +lazy val docs = project("docs") .enablePlugins(GhpagesPlugin, ParadoxSitePlugin, ParadoxMaterialThemePlugin) .settings( paradoxProperties in Paradox ++= Map( "github.base_url" -> scmInfo.value.get.browseUrl.toString ), ParadoxMaterialThemePlugin.paradoxMaterialThemeSettings(Paradox), - paradoxMaterialTheme in Paradox := { - (paradoxMaterialTheme in Paradox).value + Paradox / paradoxMaterialTheme := { + (Paradox / paradoxMaterialTheme).value .withRepository(scmInfo.value.get.browseUrl.toURI) .withColor("indigo", "indigo") } ) + +def project(name: String, plugged: AutoPlugin*) = { + val unplugged = Seq(ScriptedPlugin).filterNot(plugged.toSet) + Project(id = name, base = file(name)) + .disablePlugins(unplugged: _*) + .settings( + crossSbtVersions := List(Versions.sbt013, Versions.sbt1), + scalaVersion := { + (pluginCrossBuild / sbtBinaryVersion).value match { + case "0.13" => Versions.scala210 + case _ => Versions.scala212 + } + } + ) +} diff --git a/project/plugins.sbt b/project/plugins.sbt index be40196..e8e8b4f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,3 +2,5 @@ addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.3.2") addSbtPlugin("io.github.jonas" % "sbt-paradox-material-theme" % "0.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2") + +libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value diff --git a/sbt-scala-native-bindgen/src/main/scala/org/scalanative/bindgen/sbt/ScalaNativeBindgenPlugin.scala b/sbt-scala-native-bindgen/src/main/scala/org/scalanative/bindgen/sbt/ScalaNativeBindgenPlugin.scala new file mode 100644 index 0000000..0d7567a --- /dev/null +++ b/sbt-scala-native-bindgen/src/main/scala/org/scalanative/bindgen/sbt/ScalaNativeBindgenPlugin.scala @@ -0,0 +1,98 @@ +package org.scalanative.bindgen.sbt + +import sbt._ +import sbt.Keys._ + +import org.scalanative.bindgen.Bindgen + +/** + * Generate Scala bindings from C headers. + * + * == Usage == + * + * This plugin must be explicitly enabled. To enable it add the following line + * to your `.sbt` file: + * {{{ + * enablePlugins(ScalaNativeBindgenPlugin) + * }}} + * + * By default, the plugin reads the configured header file and generates + * a single Scala source file in the managed source directory. + * + * See the [[https://github.com/kornilova-l/scala-native-bindgen/tree/master/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate example project]]. + * + * == Configuration == + * + * Keys are defined in [[ScalaNativeBindgenPlugin.autoImport]]. + * + * - `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. + * + * @example + * {{{ + * nativeBindgenHeader in Compile := file("/usr/include/ctype.h") + * nativeBindgenPackage in Compile := Some("org.example.app") + * name in (Compile, nativeBindgen) := "ctype" + * }}} + */ +object ScalaNativeBindgenPlugin extends AutoPlugin { + + object autoImport { + val nativeBindgenPath = + taskKey[Option[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") + } + import autoImport._ + + override def requires = plugins.JvmPlugin + + override def projectSettings: Seq[Setting[_]] = + nativeBindgenScopedSettings(Compile) + + private implicit class BindgenOps(val bindgen: Bindgen) extends AnyVal { + def maybe[T](opt: Option[T], f: Bindgen => T => Bindgen): Bindgen = + opt match { + case None => bindgen + case Some(value) => f(bindgen)(value) + } + } + + def nativeBindgenScopedSettings(conf: Configuration): Seq[Setting[_]] = + inConfig(conf)( + Seq( + nativeBindgenHeader := { + sys.error("nativeBindgenHeader not configured") + }, + nativeBindgenPath := None, + nativeBindgenPackage := None, + nativeBindgenExclude := None, + resourceDirectories in nativeBindgen := resourceDirectories.value, + sourceGenerators += Def.task { Seq(nativeBindgen.value) }, + name in nativeBindgen := "ScalaNativeBindgen", + nativeBindgen := { + val output = sourceManaged.value / "sbt-scala-native-bindgen" / "nativeBindgen.scala" + + Bindgen() + .bindgenExecutable(nativeBindgenPath.value.get) + .header(nativeBindgenHeader.value) + .name((name in nativeBindgen).value) + .maybe(nativeBindgenLink.value, _.link) + .maybe(nativeBindgenPackage.value, _.packageName) + .maybe(nativeBindgenExclude.value, _.excludePrefix) + .generate() + .writeToFile(output) + + output + } + )) +} diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt new file mode 100644 index 0000000..b872b60 --- /dev/null +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt @@ -0,0 +1,35 @@ +name := "test" +organization := "org.scalanative.bindgen.sbt.test" +enablePlugins(ScalaNativeBindgenPlugin) +scalaVersion := "2.11.12" + +inConfig(Compile)( + Def.settings( + nativeBindgenPath := Option(System.getProperty("bindgen.path")).map(file), + nativeBindgenHeader := (resourceDirectory in Compile).value / "header.h", + nativeBindgenPackage := Some("org.example.app"), + nativeBindgenLink := Some("app"), + nativeBindgenExclude := Some("__"), + name in nativeBindgen := "AppAPI" + )) + +TaskKey[Unit]("check") := { + val file = (nativeBindgen in Compile).value + val expected = + """package org.example.app + | + |import scala.scalanative._ + |import scala.scalanative.native._ + | + |@native.link("app") + |@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 + |} + """.stripMargin + + assert(file.exists) + assert(IO.read(file).trim == expected.trim) +} diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/project/plugins.sbt b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/project/plugins.sbt new file mode 100644 index 0000000..7787326 --- /dev/null +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/project/plugins.sbt @@ -0,0 +1,3 @@ +addSbtPlugin( + "org.scalanative.bindgen" % "sbt-scala-native-bindgen" % sys.props( + "plugin.version")) diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/header.h b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/header.h new file mode 100644 index 0000000..606c606 --- /dev/null +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/header.h @@ -0,0 +1,3 @@ +int access(const char *path, int mode); +int read(int fildes, void *buf, int nbyte); +int printf(const char *restrict format, ...); diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test new file mode 100644 index 0000000..15675b1 --- /dev/null +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test @@ -0,0 +1 @@ +> check diff --git a/scripts/test.sh b/scripts/test.sh index 32e27de..8f3e3cd 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -13,4 +13,4 @@ if [[ ! -e bindgen/target/.llvm-version ]] || [[ "$(