From 0b0124676ff1b2cf3bae4a5dc9142f478ebb8d3f Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Thu, 26 Jul 2018 22:20:42 -0400 Subject: [PATCH 1/2] Support custom target folder for generated code --- .../bindgen/sbt/ScalaNativeBindgenPlugin.scala | 10 ++++------ .../src/sbt-test/bindgen/generate/build.sbt | 3 +++ .../src/sbt-test/bindgen/generate/test | 4 ++++ 3 files changed, 11 insertions(+), 6 deletions(-) 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..cd9cde6 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 @@ -30,17 +30,13 @@ import org.scalanative.bindgen.Bindgen * * - `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. - * + * - `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 @@ -134,8 +130,10 @@ object ScalaNativeBindgenPlugin extends AutoPlugin { nativeBindgenExclude := None, sourceGenerators += Def.task { Seq(nativeBindgen.value) }, name in nativeBindgen := "ScalaNativeBindgen", + target in nativeBindgen := sourceManaged.value / "sbt-scala-native-bindgen", nativeBindgen := { - val output = sourceManaged.value / "sbt-scala-native-bindgen" / "ScalaNativeBindgen.scala" + val fileName = (name in nativeBindgen).value + ".scala" + val output = (target in nativeBindgen).value / fileName Bindgen() .bindgenExecutable(nativeBindgenPath.value) 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..81193a7 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 @@ -13,6 +13,9 @@ inConfig(Compile)( name in nativeBindgen := "AppAPI" )) +val natigeBindgenCustomTarget = SettingKey[File]("natigeBindgenCustomTarget") +SettingKey[File]("natigeBindgenCustomTarget") := baseDirectory.value / "src/main/scala/org/example" + TaskKey[Unit]("check") := { val file = (nativeBindgen in Compile).value val expected = 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..0253717 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,5 @@ > check +$ exists target/scala-2.11/src_managed/main/sbt-scala-native-bindgen/AppAPI.scala +> set target in (Compile, nativeBindgen) := natigeBindgenCustomTarget.value +> check +$ exists src/main/scala/org/example/AppAPI.scala \ No newline at end of file From 0595458860719a84e28dfa9b897504a033dfd3c6 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Thu, 26 Jul 2018 23:54:40 -0400 Subject: [PATCH 2/2] Allow multiple bindings per nativeBindgen task invocation --- .../sbt/ScalaNativeBindgenPlugin.scala | 142 +++++++++--------- .../src/sbt-test/bindgen/generate/build.sbt | 87 ++++++++--- .../generate/src/main/resources/core.h | 2 + .../src/main/resources/{header.h => stdlib.h} | 1 + .../src/sbt-test/bindgen/generate/test | 15 +- 5 files changed, 153 insertions(+), 94 deletions(-) create mode 100644 sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/core.h rename sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/src/main/resources/{header.h => stdlib.h} (86%) 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 cd9cde6..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,14 +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 @@ -49,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._ @@ -66,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 = @@ -123,29 +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 fileName = (name in nativeBindgen).value + ".scala" - val output = (target in nativeBindgen).value / fileName - - 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 81193a7..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,33 +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("__") + ) + ) )) -val natigeBindgenCustomTarget = SettingKey[File]("natigeBindgenCustomTarget") -SettingKey[File]("natigeBindgenCustomTarget") := baseDirectory.value / "src/main/scala/org/example" +val nativeBindgenCustomTarget = SettingKey[File]("nativeBindgenCustomTarget") +nativeBindgenCustomTarget := baseDirectory.value / "src/main/scala/org/example" -TaskKey[Unit]("check") := { - val file = (nativeBindgen in Compile).value - val expected = - """package org.example.app +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 0253717..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,5 +1,10 @@ -> check -$ exists target/scala-2.11/src_managed/main/sbt-scala-native-bindgen/AppAPI.scala -> set target in (Compile, nativeBindgen) := natigeBindgenCustomTarget.value -> check -$ exists src/main/scala/org/example/AppAPI.scala \ No newline at end of file +> 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