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 index 0535939..6407c07 100644 --- 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 @@ -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 @@ -53,16 +45,19 @@ 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._ @@ -70,45 +65,45 @@ object ScalaNativeBindgenPlugin extends AutoPlugin { 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 = @@ -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 + } } )) } 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 index d27ab1c..fb8d346 100644 --- a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt @@ -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) } diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/core.h b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/core.h new file mode 100644 index 0000000..a33e0dd --- /dev/null +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/core.h @@ -0,0 +1,2 @@ +int count_words(const char *text); +void __not_excluded(void); \ No newline at end of file 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/stdlib.h similarity index 86% rename from sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/header.h rename to sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/stdlib.h index 606c606..1784bc6 100644 --- 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/stdlib.h @@ -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); \ No newline at end of file diff --git a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test index 15675b1..1e8b280 100644 --- a/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test +++ b/sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/test @@ -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