diff --git a/.gitignore b/.gitignore index eb5a316..2f7896d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target +target/ diff --git a/.travis.yml b/.travis.yml index 1bb6b05..aafc9fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,12 @@ sudo: required compiler: gcc script: - - docker-compose build ubuntu-16.04-llvm-$LLVM_VERSION - - docker run --rm -ti scala-native-bindgen:ubuntu-16.04-llvm-$LLVM_VERSION /usr/include/ctype.h -name ctype -- - - docker run --rm -ti --entrypoint /src/target/scalaBindgenTest scala-native-bindgen:ubuntu-16.04-llvm-$LLVM_VERSION + - docker-compose build $TEST_ENV + - docker run --rm -ti scala-native-bindgen:$TEST_ENV /usr/include/ctype.h -name ctype -- + - docker-compose run --rm sbt-test matrix: include: - - env: LLVM_VERSION=dev - - env: LLVM_VERSION=6.0 - - env: LLVM_VERSION=5.0 + - env: TEST_ENV=ubuntu-16.04-llvm-dev + - env: TEST_ENV=ubuntu-16.04-llvm-6.0 + - env: TEST_ENV=ubuntu-16.04-llvm-5.0 diff --git a/Dockerfile b/Dockerfile index c40f7ea..16edfbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,11 @@ FROM ubuntu:$UBUNTU_VERSION RUN set -x \ && apt update \ - && apt install -y curl build-essential \ + && apt install -y apt-transport-https \ + && echo "deb https://dl.bintray.com/sbt/debian /" > /etc/apt/sources.list.d/sbt.list \ + && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 \ + && apt update \ + && apt install -y curl build-essential openjdk-8-jdk-headless sbt \ && rm -rf /var/lib/apt/lists/* WORKDIR /cmake @@ -20,7 +24,7 @@ ARG LLVM_DEB_COMPONENT=-$LLVM_VERSION RUN set -x \ && curl https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \ && . /etc/lsb-release \ - && echo "deb http://apt.llvm.org/$DISTRIB_CODENAME/ llvm-toolchain-$DISTRIB_CODENAME$LLVM_DEB_COMPONENT main" > /etc/apt/sources.list.d/llvm.list \ + && echo "deb https://apt.llvm.org/$DISTRIB_CODENAME/ llvm-toolchain-$DISTRIB_CODENAME$LLVM_DEB_COMPONENT main" > /etc/apt/sources.list.d/llvm.list \ && apt update \ && apt install -y clang-$LLVM_VERSION libclang-$LLVM_VERSION-dev make \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index 80e16b2..d825e79 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,30 @@ Running the previous command wild also yield warnings along with the translation ## Building -Building this tool requires LLVM and Clang. Ensure that `llvm-config` is in your path. +Building this tool requires [CMake], [LLVM] and [Clang]. See the [Scala +Native setup guide] for instructions on installing the dependencies. ```sh -# Validate LLVM installation -llvm-config --version --cmakedir --cxxflags --ldflags - mkdir -p target cd target cmake .. make -./scalaBindgen /usr/include/ctype.h -name ctype +./scalaBindgen /usr/include/ctype.h -name ctype -- +``` + + [CMake]: https://cmake.org/ + [LLVM]: https://llvm.org/ + [Clang]: https://clang.llvm.org/ + [Scala Native setup guide]: http://www.scala-native.org/en/latest/user/setup.html + +## Testing + +The tests assumes that the above instructions for building has been +followed. + +```sh +cd tests +sbt test ``` ## License diff --git a/docker-compose.yml b/docker-compose.yml index 6ed8b45..1c12432 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,3 +26,14 @@ services: - UBUNTU_VERSION=16.04 - LLVM_VERSION=5.0 + sbt-test: + image: scala-native-bindgen:${TEST_ENV} + entrypoint: + - sh + - -c + - | + cd /src/tests + sbt compile test + volumes: + - ${HOME}/.ivy2:/root/.ivy2 + - ${HOME}/.sbt:/root/.sbt diff --git a/tests/build.sbt b/tests/build.sbt new file mode 100644 index 0000000..36e6984 --- /dev/null +++ b/tests/build.sbt @@ -0,0 +1,29 @@ +inThisBuild( + Def.settings( + organization := "org.scalanative.bindgen", + version := "0.1-SNAPSHOT", + scalaVersion := "2.11.12", + scalacOptions ++= Seq( + "-deprecation", + "-unchecked", + "-feature", + "-encoding", + "utf8" + ) + )) + +val `scala-native-bindgen-tests` = project + .in(file(".")) + .settings( + fork in Test := true, + javaOptions in Test += "-Dbindgen.path=" + file("../target/scalaBindgen"), + watchSources += WatchSource( + baseDirectory.value / "samples", + "*.h" || "*.scala", + NothingFilter + ), + libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test + ) + +val samples = project + .enablePlugins(ScalaNativePlugin) diff --git a/tests/project/build.properties b/tests/project/build.properties new file mode 100644 index 0000000..bf24547 --- /dev/null +++ b/tests/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.1.6 \ No newline at end of file diff --git a/tests/project/plugins.sbt b/tests/project/plugins.sbt new file mode 100644 index 0000000..afc9d5a --- /dev/null +++ b/tests/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") diff --git a/tests/samples/Function.h b/tests/samples/Function.h new file mode 100644 index 0000000..4a848cb --- /dev/null +++ b/tests/samples/Function.h @@ -0,0 +1,6 @@ +int no_args(); +int void_arg(void); +void one_arg(int a); +void *two_args(float a, int b); +void anonymous_args(float, int); +double variadic_args(double a, void* b, ...); diff --git a/tests/samples/Function.scala b/tests/samples/Function.scala new file mode 100644 index 0000000..fb2bc86 --- /dev/null +++ b/tests/samples/Function.scala @@ -0,0 +1,14 @@ +import scala.scalanative._ +import scala.scalanative.native._ +import scala.scalanative.native.Nat._ + +@native.link("Function") +@native.extern +object Function { + def no_args(): native.CInt = native.extern + def void_arg(): native.CInt = native.extern + def one_arg(a: native.CInt): Unit = native.extern + def two_args(a: native.CFloat, b: native.CInt): native.Ptr[Byte] = native.extern + def anonymous_args(anonymous0: native.CFloat, anonymous1: native.CInt): Unit = native.extern + def variadic_args(a: native.CDouble, b: native.Ptr[Byte], varArgs: native.CVararg*): native.CDouble = native.extern +} diff --git a/tests/samples/NativeTypes.h b/tests/samples/NativeTypes.h new file mode 100644 index 0000000..5100e27 --- /dev/null +++ b/tests/samples/NativeTypes.h @@ -0,0 +1,36 @@ +/* + * Until generated type aliases are pruned the following dummy typedefs + * are here to avoid including standard headers: + * + * #include + * #include + */ +typedef unsigned int size_t; +typedef unsigned int ptrdiff_t; +typedef unsigned short char16_t; +typedef unsigned int char32_t; + +typedef void void_type; +typedef char char_type; +typedef signed char signed_char_type; +typedef unsigned char unsigned_char_type; +typedef short short_type; +typedef unsigned short unsigned_short_type; +typedef int int_type; +typedef unsigned int unsigned_int_type; +typedef long long_type; +typedef long int long_int_type; +typedef unsigned long unsigned_long_type; +typedef unsigned long int unsigned_long_int_type; +typedef long long long_long_type; +typedef unsigned long long unsigned_long_long_type; +typedef float float_type; +typedef double double_type; +typedef void * ptr_byte_type; +typedef int * ptr_int_type; +typedef char * cstring_type; + +typedef size_t size_t_type; +typedef ptrdiff_t ptrdiff_t_type; +typedef char16_t char16_t_type; +typedef char32_t char32_t_type; \ No newline at end of file diff --git a/tests/samples/NativeTypes.scala b/tests/samples/NativeTypes.scala new file mode 100644 index 0000000..094b7f8 --- /dev/null +++ b/tests/samples/NativeTypes.scala @@ -0,0 +1,35 @@ +import scala.scalanative._ +import scala.scalanative.native._ +import scala.scalanative.native.Nat._ + +@native.link("NativeTypes") +@native.extern +object NativeTypes { + type size_t = native.CUnsignedInt + type ptrdiff_t = native.CUnsignedInt + type char16_t = native.CUnsignedShort + type char32_t = native.CUnsignedInt + type void_type = Unit + type char_type = native.CChar + type signed_char_type = native.CSignedChar + type unsigned_char_type = native.CUnsignedChar + type short_type = native.CShort + type unsigned_short_type = native.CUnsignedShort + type int_type = native.CInt + type unsigned_int_type = native.CUnsignedInt + type long_type = native.CLong + type long_int_type = native.CLong + type unsigned_long_type = native.CUnsignedLong + type unsigned_long_int_type = native.CUnsignedLong + type long_long_type = native.CLongLong + type unsigned_long_long_type = native.CUnsignedLongLong + type float_type = native.CFloat + type double_type = native.CDouble + type ptr_byte_type = native.Ptr[Byte] + type ptr_int_type = native.Ptr[native.CInt] + type cstring_type = native.CString + type size_t_type = native.CSize + type ptrdiff_t_type = native.CPtrDiff + type char16_t_type = native.CChar16 + type char32_t_type = native.CChar32 +} diff --git a/tests/samples/Struct.h b/tests/samples/Struct.h new file mode 100644 index 0000000..e9f9258 --- /dev/null +++ b/tests/samples/Struct.h @@ -0,0 +1,6 @@ +struct point { + int x; + int y; +}; + +typedef struct point *point_s; diff --git a/tests/samples/Struct.scala b/tests/samples/Struct.scala new file mode 100644 index 0000000..d064aee --- /dev/null +++ b/tests/samples/Struct.scala @@ -0,0 +1,24 @@ +import scala.scalanative._ +import scala.scalanative.native._ +import scala.scalanative.native.Nat._ + +@native.link("Struct") +@native.extern +object Struct { + type struct_point = native.CStruct2[native.CInt, native.CInt] + type point_s = native.Ptr[struct_point] +} + +import Struct._ + +object StructHelpers { + implicit class struct_point_ops(val p: native.Ptr[struct_point]) extends AnyVal { + def x: native.CInt = !p._1 + def x_=(value: native.CInt):Unit = !p._1 = value + def y: native.CInt = !p._2 + def y_=(value: native.CInt):Unit = !p._2 = value + } + + def struct_point()(implicit z: native.Zone): native.Ptr[struct_point] = native.alloc[struct_point] + +} diff --git a/tests/samples/Typedef.h b/tests/samples/Typedef.h new file mode 100644 index 0000000..d481e6f --- /dev/null +++ b/tests/samples/Typedef.h @@ -0,0 +1,18 @@ +enum days { + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + SUNDAY, +}; + +typedef enum { + OFF, + ON +} toggle_e; + +typedef int (*int2int)(int); +typedef const char * (*day2string)(enum days); +typedef void (*toggle)(toggle_e state); \ No newline at end of file diff --git a/tests/samples/Typedef.scala b/tests/samples/Typedef.scala new file mode 100644 index 0000000..f7588c8 --- /dev/null +++ b/tests/samples/Typedef.scala @@ -0,0 +1,28 @@ +import scala.scalanative._ +import scala.scalanative.native._ +import scala.scalanative.native.Nat._ + +@native.link("Typedef") +@native.extern +object Typedef { + type enum_days = native.CInt + type enum_toggle_e = native.CInt + type toggle_e = enum_toggle_e + type int2int = native.CFunctionPtr1[native.CInt, native.CInt] + type day2string = native.CFunctionPtr1[enum_days, native.CString] + type toggle = native.CFunctionPtr1[toggle_e, Unit] +} + +import Typedef._ + +object TypedefEnums { + final val enum_days_MONDAY = 0 + final val enum_days_TUESDAY = 1 + final val enum_days_WEDNESDAY = 2 + final val enum_days_THURSDAY = 3 + final val enum_days_FRIDAY = 4 + final val enum_days_SATURDAY = 5 + final val enum_days_SUNDAY = 6 + final val enum_toggle_e_OFF = 0 + final val enum_toggle_e_ON = 1 +} diff --git a/tests/src/test/scala/org/scalanative/bindgen/BindgenSpec.scala b/tests/src/test/scala/org/scalanative/bindgen/BindgenSpec.scala new file mode 100644 index 0000000..921e7da --- /dev/null +++ b/tests/src/test/scala/org/scalanative/bindgen/BindgenSpec.scala @@ -0,0 +1,53 @@ +package org.scalanative.bindgen + +import java.io.{File, PrintWriter} +import org.scalatest.FunSpec +import scala.io.Source +import scala.sys.process._ + +class BindgenSpec extends FunSpec { + describe("Bindgen") { + val bindgenPath = System.getProperty("bindgen.path") + val inputDirectory = new File("samples") + + val outputDir = new File("target/bindgen-samples") + Option(outputDir.listFiles()).foreach(_.foreach(_.delete())) + outputDir.mkdirs() + + it("should exist") { + assert(new File(bindgenPath).exists) + } + + def bindgen(inputFile: File, name: String, outputFile: File): Unit = { + val cmd = Seq( + bindgenPath, + inputFile.getAbsolutePath, + "-name", + name, + "--" + ) + val output = Process(cmd).lineStream.mkString("\n") + + new PrintWriter(outputFile) { + write(output) + close() + } + } + + def contentOf(file: File) = + Source.fromFile(file).getLines.mkString("\n").trim() + + for (input <- inputDirectory.listFiles() if input.getName.endsWith(".h")) { + it(s"should generate bindings for ${input.getName}") { + val testName = input.getName.replace(".h", "") + val expected = new File(inputDirectory, testName + ".scala") + val output = new File(outputDir, testName + ".scala") + + bindgen(input, testName, output) + + assert(output.exists()) + assert(contentOf(output) == contentOf(expected)) + } + } + } +}