Skip to content

Commit efcdf01

Browse files
authored
Merge pull request #136 from kornilova-l/sbt-plugin
sbt plugin improvements
2 parents 942598b + 0595458 commit efcdf01

File tree

5 files changed

+156
-92
lines changed

5 files changed

+156
-92
lines changed

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

+73-75
Original file line numberDiff line numberDiff line change
@@ -29,18 +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-
*
34-
* - `nativeBindgenPackage`: Package of the enclosing object.
35-
* No package by default.
36-
*
37-
* - `name in nativeBindgen`: Name of the enclosing object.
38-
*
32+
* - `nativeBindings`: Settings for each binding to generate.
33+
* - `target in nativeBindgen`: Output folder of the generated code.
3934
* - `version in nativeBindgen`: Version of the `scala-native-bindgen`
4035
* to use when automatically downloading the executable.
41-
*
42-
* - `nativeBindgenLink`: Name of library to be linked.
43-
*
4436
* - `nativeBindgen`: Generate Scala Native bindings.
4537
*
4638
* @example
@@ -53,62 +45,65 @@ import org.scalanative.bindgen.Bindgen
5345
object ScalaNativeBindgenPlugin extends AutoPlugin {
5446

5547
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+
)
5655
val ScalaNativeBindgen = config("scala-native-bindgen").hide
5756
val nativeBindgenPath =
5857
taskKey[File]("Path to the scala-native-bindgen executable")
59-
val nativeBindgenHeader = taskKey[File]("C header file")
60-
val nativeBindgenPackage =
61-
settingKey[Option[String]]("Package for the generated code")
62-
val nativeBindgenLink =
63-
settingKey[Option[String]]("Name of library to be linked")
64-
val nativeBindgenExclude = settingKey[Option[String]]("Exclude prefix")
65-
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")
6661
}
6762
import autoImport._
6863

6964
override def requires = plugins.JvmPlugin
7065

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

113108
private implicit class BindgenOps(val bindgen: Bindgen) extends AnyVal {
114109
def maybe[T](opt: Option[T], f: Bindgen => T => Bindgen): Bindgen =
@@ -127,27 +122,30 @@ object ScalaNativeBindgenPlugin extends AutoPlugin {
127122
def nativeBindgenScopedSettings(conf: Configuration): Seq[Setting[_]] =
128123
inConfig(conf)(
129124
Def.settings(
130-
nativeBindgenHeader := {
131-
sys.error("nativeBindgenHeader not configured")
132-
},
133-
nativeBindgenPackage := None,
134-
nativeBindgenExclude := None,
135-
sourceGenerators += Def.task { Seq(nativeBindgen.value) },
136-
name in nativeBindgen := "ScalaNativeBindgen",
125+
nativeBindings := Seq.empty,
126+
sourceGenerators += Def.task { nativeBindgen.value },
127+
target in nativeBindgen := sourceManaged.value / "sbt-scala-native-bindgen",
137128
nativeBindgen := {
138-
val output = sourceManaged.value / "sbt-scala-native-bindgen" / "ScalaNativeBindgen.scala"
139-
140-
Bindgen()
141-
.bindgenExecutable(nativeBindgenPath.value)
142-
.header(nativeBindgenHeader.value)
143-
.name((name in nativeBindgen).value)
144-
.maybe(nativeBindgenLink.value, _.link)
145-
.maybe(nativeBindgenPackage.value, _.packageName)
146-
.maybe(nativeBindgenExclude.value, _.excludePrefix)
147-
.generate()
148-
.writeToFile(output)
149-
150-
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+
}
151149
}
152150
))
153151
}

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

+70-16
Original file line numberDiff line numberDiff line change
@@ -6,30 +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-
TaskKey[Unit]("check") := {
17-
val file = (nativeBindgen in Compile).value
18-
val expected =
19-
"""package org.example.app
20+
val nativeBindgenCustomTarget = SettingKey[File]("nativeBindgenCustomTarget")
21+
nativeBindgenCustomTarget := baseDirectory.value / "src/main/scala/org/example"
22+
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
2076
|
2177
|import scala.scalanative._
2278
|import scala.scalanative.native._
2379
|
24-
|@native.link("app")
80+
|@native.link("core")
2581
|@native.extern
26-
|object AppAPI {
27-
| def access(path: native.CString, mode: native.CInt): native.CInt = native.extern
28-
| def read(fildes: native.CInt, buf: native.Ptr[Byte], nbyte: native.CInt): native.CInt = native.extern
29-
| 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
3085
|}
3186
""".stripMargin
3287

33-
assert(file.exists)
34-
assert(IO.read(file).trim == expected.trim)
88+
assertFileContent(files(1), CoreOutput)
3589
}
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);
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
> check
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)