aboutsummaryrefslogtreecommitdiff
path: root/plugins/javah/Javah.scala
blob: cc8aec174738f6ade53af31269e2d2d9345ed173 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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()
    }
  }

}