Skip to content

Commit 0595458

Browse files
committed
Allow multiple bindings per nativeBindgen task invocation
1 parent 0b01246 commit 0595458

File tree

5 files changed

+153
-94
lines changed

5 files changed

+153
-94
lines changed

sbt-scala-native-bindgen/src/main/scala/org/scalanative/bindgen/sbt/ScalaNativeBindgenPlugin.scala

Lines changed: 71 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,10 @@ import org.scalanative.bindgen.Bindgen
2929
* Keys are defined in [[ScalaNativeBindgenPlugin.autoImport]].
3030
*
3131
* - `nativeBindgenPath`: Path to the `scala-native-bindgen` executable.
32-
* - `nativeBindgenHeader`: The C header file to read.
33-
* - `nativeBindgenPackage`: Package of the enclosing object.
34-
* No package by default.
35-
* - `name in nativeBindgen`: Name of the enclosing object.
32+
* - `nativeBindings`: Settings for each binding to generate.
3633
* - `target in nativeBindgen`: Output folder of the generated code.
3734
* - `version in nativeBindgen`: Version of the `scala-native-bindgen`
3835
* to use when automatically downloading the executable.
39-
* - `nativeBindgenLink`: Name of library to be linked.
4036
* - `nativeBindgen`: Generate Scala Native bindings.
4137
*
4238
* @example
@@ -49,62 +45,65 @@ import org.scalanative.bindgen.Bindgen
4945
object ScalaNativeBindgenPlugin extends AutoPlugin {
5046

5147
object autoImport {
48+
case class NativeBinding(
49+
name: String,
50+
header: File,
51+
packageName: Option[String],
52+
link: Option[String],
53+
excludePrefix: Option[String]
54+
)
5255
val ScalaNativeBindgen = config("scala-native-bindgen").hide
5356
val nativeBindgenPath =
5457
taskKey[File]("Path to the scala-native-bindgen executable")
55-
val nativeBindgenHeader = taskKey[File]("C header file")
56-
val nativeBindgenPackage =
57-
settingKey[Option[String]]("Package for the generated code")
58-
val nativeBindgenLink =
59-
settingKey[Option[String]]("Name of library to be linked")
60-
val nativeBindgenExclude = settingKey[Option[String]]("Exclude prefix")
61-
val nativeBindgen = taskKey[File]("Generate Scala Native bindings")
58+
val nativeBindings =
59+
settingKey[Seq[NativeBinding]]("Configuration for each bindings")
60+
val nativeBindgen = taskKey[Seq[File]]("Generate Scala Native bindings")
6261
}
6362
import autoImport._
6463

6564
override def requires = plugins.JvmPlugin
6665

6766
override def projectSettings: Seq[Setting[_]] =
6867
inConfig(ScalaNativeBindgen)(Defaults.configSettings) ++
69-
nativeBindgenScopedSettings(Compile) ++
70-
Def.settings(
71-
ivyConfigurations += ScalaNativeBindgen,
72-
version in nativeBindgen := BuildInfo.version,
73-
libraryDependencies ++= {
74-
artifactName.map { name =>
75-
val bindgenVersion = (version in nativeBindgen).value
76-
val url =
77-
s"${BuildInfo.projectUrl}/releases/download/v$bindgenVersion/$name"
78-
79-
BuildInfo.organization % name % bindgenVersion % ScalaNativeBindgen from (url)
80-
}.toSeq
81-
},
82-
nativeBindgenPath := {
83-
val scalaNativeBindgenUpdate = (update in ScalaNativeBindgen).value
84-
85-
val artifactFile = artifactName match {
86-
case None =>
87-
sys.error(
88-
"No downloadable binaries available for your OS, " +
89-
"please provide path via `nativeBindgenPath`")
90-
case Some(name) =>
91-
scalaNativeBindgenUpdate
92-
.select(artifact = artifactFilter(name = name))
93-
.head
94-
}
95-
96-
// Set the executable bit on the expected path to fail if it doesn't exist
97-
for (view <- Option(
98-
Files.getFileAttributeView(artifactFile.toPath,
99-
classOf[PosixFileAttributeView]))) {
100-
val permissions = view.readAttributes.permissions
101-
if (permissions.add(PosixFilePermission.OWNER_EXECUTE))
102-
view.setPermissions(permissions)
68+
nativeBindgenScopedSettings(Compile) ++
69+
Def.settings(
70+
ivyConfigurations += ScalaNativeBindgen,
71+
version in nativeBindgen := BuildInfo.version,
72+
libraryDependencies ++= {
73+
artifactName.map { name =>
74+
val bindgenVersion = (version in nativeBindgen).value
75+
val url =
76+
s"${BuildInfo.projectUrl}/releases/download/v$bindgenVersion/$name"
77+
78+
BuildInfo.organization % name % bindgenVersion % ScalaNativeBindgen from (url)
79+
}.toSeq
80+
},
81+
nativeBindgenPath := {
82+
val scalaNativeBindgenUpdate = (update in ScalaNativeBindgen).value
83+
84+
val artifactFile = artifactName match {
85+
case None =>
86+
sys.error(
87+
"No downloadable binaries available for your OS, " +
88+
"please provide path via `nativeBindgenPath`")
89+
case Some(name) =>
90+
scalaNativeBindgenUpdate
91+
.select(artifact = artifactFilter(name = name))
92+
.head
93+
}
94+
95+
// Set the executable bit on the expected path to fail if it doesn't exist
96+
for (view <- Option(
97+
Files.getFileAttributeView(artifactFile.toPath,
98+
classOf[PosixFileAttributeView]))) {
99+
val permissions = view.readAttributes.permissions
100+
if (permissions.add(PosixFilePermission.OWNER_EXECUTE))
101+
view.setPermissions(permissions)
102+
}
103+
104+
artifactFile
103105
}
104-
105-
artifactFile
106-
}
107-
)
106+
)
108107

109108
private implicit class BindgenOps(val bindgen: Bindgen) extends AnyVal {
110109
def maybe[T](opt: Option[T], f: Bindgen => T => Bindgen): Bindgen =
@@ -123,29 +122,30 @@ object ScalaNativeBindgenPlugin extends AutoPlugin {
123122
def nativeBindgenScopedSettings(conf: Configuration): Seq[Setting[_]] =
124123
inConfig(conf)(
125124
Def.settings(
126-
nativeBindgenHeader := {
127-
sys.error("nativeBindgenHeader not configured")
128-
},
129-
nativeBindgenPackage := None,
130-
nativeBindgenExclude := None,
131-
sourceGenerators += Def.task { Seq(nativeBindgen.value) },
132-
name in nativeBindgen := "ScalaNativeBindgen",
125+
nativeBindings := Seq.empty,
126+
sourceGenerators += Def.task { nativeBindgen.value },
133127
target in nativeBindgen := sourceManaged.value / "sbt-scala-native-bindgen",
134128
nativeBindgen := {
135-
val fileName = (name in nativeBindgen).value + ".scala"
136-
val output = (target in nativeBindgen).value / fileName
137-
138-
Bindgen()
139-
.bindgenExecutable(nativeBindgenPath.value)
140-
.header(nativeBindgenHeader.value)
141-
.name((name in nativeBindgen).value)
142-
.maybe(nativeBindgenLink.value, _.link)
143-
.maybe(nativeBindgenPackage.value, _.packageName)
144-
.maybe(nativeBindgenExclude.value, _.excludePrefix)
145-
.generate()
146-
.writeToFile(output)
147-
148-
output
129+
val bindgenPath = nativeBindgenPath.value
130+
val bindings = nativeBindings.value
131+
val outputDirectory = (target in nativeBindgen).value
132+
133+
bindings.map {
134+
binding =>
135+
val output = outputDirectory / s"${binding.name}.scala"
136+
137+
Bindgen()
138+
.bindgenExecutable(bindgenPath)
139+
.header(binding.header)
140+
.name(binding.name)
141+
.maybe(binding.link, _.link)
142+
.maybe(binding.packageName, _.packageName)
143+
.maybe(binding.excludePrefix, _.excludePrefix)
144+
.generate()
145+
.writeToFile(output)
146+
147+
output
148+
}
149149
}
150150
))
151151
}

sbt-scala-native-bindgen/src/sbt-test/bindgen/generate/build.sbt

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,84 @@ scalaVersion := "2.11.12"
66
inConfig(Compile)(
77
Def.settings(
88
nativeBindgenPath := file(System.getProperty("bindgen.path")),
9-
nativeBindgenHeader := (resourceDirectory in Compile).value / "header.h",
10-
nativeBindgenPackage := Some("org.example.app"),
11-
nativeBindgenLink := Some("app"),
12-
nativeBindgenExclude := Some("__"),
13-
name in nativeBindgen := "AppAPI"
9+
nativeBindings := Seq(
10+
NativeBinding(
11+
name = "stdlib",
12+
header = (resourceDirectory in Compile).value / "stdlib.h",
13+
packageName = Some("org.example.app.stdlib"),
14+
link = None,
15+
excludePrefix = Some("__")
16+
)
17+
)
1418
))
1519

16-
val natigeBindgenCustomTarget = SettingKey[File]("natigeBindgenCustomTarget")
17-
SettingKey[File]("natigeBindgenCustomTarget") := baseDirectory.value / "src/main/scala/org/example"
20+
val nativeBindgenCustomTarget = SettingKey[File]("nativeBindgenCustomTarget")
21+
nativeBindgenCustomTarget := baseDirectory.value / "src/main/scala/org/example"
1822

19-
TaskKey[Unit]("check") := {
20-
val file = (nativeBindgen in Compile).value
21-
val expected =
22-
"""package org.example.app
23+
val nativeBindgenCoreBinding =
24+
SettingKey[NativeBinding]("nativeBindgenCoreBinding")
25+
nativeBindgenCoreBinding := {
26+
NativeBinding(
27+
name = "core",
28+
header = (resourceDirectory in Compile).value / "core.h",
29+
packageName = Some("org.example.app.core"),
30+
link = Some("core"),
31+
excludePrefix = None
32+
)
33+
}
34+
35+
val StdlibOutput =
36+
"""package org.example.app.stdlib
37+
|
38+
|import scala.scalanative._
39+
|import scala.scalanative.native._
40+
|
41+
|@native.extern
42+
|object stdlib {
43+
| def access(path: native.CString, mode: native.CInt): native.CInt = native.extern
44+
| def read(fildes: native.CInt, buf: native.Ptr[Byte], nbyte: native.CInt): native.CInt = native.extern
45+
| def printf(format: native.CString, varArgs: native.CVararg*): native.CInt = native.extern
46+
|}
47+
""".stripMargin
48+
49+
def assertFileContent(file: File, expected: String): Unit = {
50+
val actual = IO.read(file).trim
51+
if (actual != expected.trim) {
52+
println(s"== [ actual ${file.getName} ] ========")
53+
println(actual)
54+
println(s"== [ expected ${file.getName} ] ========")
55+
println(expected.trim)
56+
}
57+
assert(actual == expected.trim)
58+
}
59+
60+
TaskKey[Unit]("checkSingle") := {
61+
val files = (nativeBindgen in Compile).value
62+
assert(files.forall(_.exists()))
63+
assert(files.length == 1)
64+
assertFileContent(files.head, StdlibOutput)
65+
}
66+
67+
TaskKey[Unit]("checkMultiple") := {
68+
val files = (nativeBindgen in Compile).value
69+
assert(files.forall(_.exists()))
70+
assert(files.length == 2)
71+
72+
assertFileContent(files(0), StdlibOutput)
73+
74+
val CoreOutput =
75+
"""package org.example.app.core
2376
|
2477
|import scala.scalanative._
2578
|import scala.scalanative.native._
2679
|
27-
|@native.link("app")
80+
|@native.link("core")
2881
|@native.extern
29-
|object AppAPI {
30-
| def access(path: native.CString, mode: native.CInt): native.CInt = native.extern
31-
| def read(fildes: native.CInt, buf: native.Ptr[Byte], nbyte: native.CInt): native.CInt = native.extern
32-
| def printf(format: native.CString, varArgs: native.CVararg*): native.CInt = native.extern
82+
|object core {
83+
| def count_words(text: native.CString): native.CInt = native.extern
84+
| def __not_excluded(): Unit = native.extern
3385
|}
3486
""".stripMargin
3587

36-
assert(file.exists)
37-
assert(IO.read(file).trim == expected.trim)
88+
assertFileContent(files(1), CoreOutput)
3889
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
int count_words(const char *text);
2+
void __not_excluded(void);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
int access(const char *path, int mode);
22
int read(int fildes, void *buf, int nbyte);
33
int printf(const char *restrict format, ...);
4+
int __excluded(void);
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
> check
2-
$ exists target/scala-2.11/src_managed/main/sbt-scala-native-bindgen/AppAPI.scala
3-
> set target in (Compile, nativeBindgen) := natigeBindgenCustomTarget.value
4-
> check
5-
$ exists src/main/scala/org/example/AppAPI.scala
1+
> checkSingle
2+
$ exists target/scala-2.11/src_managed/main/sbt-scala-native-bindgen/stdlib.scala
3+
> set target in (Compile, nativeBindgen) := nativeBindgenCustomTarget.value
4+
> checkSingle
5+
$ exists src/main/scala/org/example/stdlib.scala
6+
> clean
7+
> set nativeBindings in Compile += nativeBindgenCoreBinding.value
8+
> checkMultiple
9+
$ exists src/main/scala/org/example/core.scala
10+
$ exists src/main/scala/org/example/stdlib.scala

0 commit comments

Comments
 (0)