From 7dc655155019f25ae087f2599c82b59694f968d1 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 3 Jul 2017 20:08:36 -0700 Subject: Add javah plugin --- plugins/javah/Javah.scala | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 plugins/javah/Javah.scala (limited to 'plugins/javah/Javah.scala') diff --git a/plugins/javah/Javah.scala b/plugins/javah/Javah.scala new file mode 100644 index 0000000..cc8aec1 --- /dev/null +++ b/plugins/javah/Javah.scala @@ -0,0 +1,97 @@ +package cbt + +import java.io.{ File, FileInputStream, Closeable } +import java.nio.file._ +import scala.collection.JavaConverters._ +import scala.collection.mutable.{ HashSet } +import scala.sys.process._ + +import org.objectweb.asm.{ ClassReader, ClassVisitor, MethodVisitor, Opcodes } + +trait Javah extends BaseBuild { + + def javah = Javah.apply(lib, println _).config( + (target / "include").toPath, + exportedClasspath.files ++ dependencyClasspath.files, + exportedClasspath.files + .flatMap(file => Files.walk(file.toPath).iterator().asScala.toSeq) + .toSet + .filter(Files.isRegularFile(_)) + .flatMap(Javah.findNativeClasses) + ) + +} + +object Javah { + + case class apply(lib: Lib, log: (String) => Unit) { + case class config(target: Path, classpath: Seq[File], nativeClasses: Set[String]) { + def apply: Set[String] = { + val cp = classpath.mkString(sys.props("path.separator")) + if (!Files.exists(target)) { + log("creating file") + } + if (!nativeClasses.isEmpty) { + log(s"headers will be generated in $target") + } + for (clazz <- nativeClasses) yield { + log(s"generating header for $clazz") + val parts = Seq( + "javah", + "-d", target.toString, + "-classpath", cp, + clazz + ) + val cmd = parts.mkString(" ") + Process(cmd).!! + } + } + } + } + + + private class NativeFinder extends ClassVisitor(Opcodes.ASM5) { + + // classes found to contain at least one @native def + val _nativeClasses = new HashSet[String] + def nativeClasses = _nativeClasses.toSet + + private var fullyQualifiedName: String = "" + + override def visit(version: Int, access: Int, name: String, signature: String, + superName: String, interfaces: Array[String]): Unit = { + fullyQualifiedName = name.replaceAll("/", ".") + } + + override def visitMethod(access: Int, name: String, desc: String, + signature: String, exceptions: Array[String]): MethodVisitor = { + + val isNative = (access & Opcodes.ACC_NATIVE) != 0 + + if (isNative) { + _nativeClasses += fullyQualifiedName + } + + null //return null, do not visit method further + } + + } + + /** Finds classes containing native implementations. + * @param classFile java class file from which classes are read + * @return all fully qualified names of classes that contain at least one member annotated + * with @native + */ + def findNativeClasses(classFile: Path): Set[String] = { + val in = Files.newInputStream(classFile) + try { + val reader = new ClassReader(in) + val finder = new NativeFinder + reader.accept(finder, 0) + finder.nativeClasses + } finally { + in.close() + } + } + +} -- cgit v1.2.3