Skip to content
This repository
Browse code

Merge pull request #115 from mkneissl/master

Run javah to generate JNI C header files.
  • Loading branch information...
commit 053ca2d4dec13d478f3212cbdf4a8f52e17a3b8e 2 parents 13a89e5 + 6e2943c
Jan Berkel authored February 08, 2012
104  src/main/scala/AndroidNdk.scala
@@ -24,13 +24,17 @@ object AndroidNdk {
24 24
   val DefaultObjDirectoryName = "obj"
25 25
   /** The list of environment variables to check for the NDK. */
26 26
   val DefaultEnvs = List("ANDROID_NDK_HOME", "ANDROID_NDK_ROOT")
27  
-
  27
+  /** The make environment variable name for the javah generated header directory. */
  28
+  val DefaultJavahOutputEnv = "SBT_MANAGED_JNI_INCLUDE"
28 29
 
29 30
   lazy val defaultSettings: Seq[Setting[_]] = inConfig(Android) (Seq (
30 31
     ndkBuildName := DefaultNdkBuildName,
31 32
     jniDirectoryName := DefaultJniDirectoryName,
32 33
     objDirectoryName := DefaultObjDirectoryName,
33  
-    ndkEnvs := DefaultEnvs
  34
+    ndkEnvs := DefaultEnvs,
  35
+    javahName := "javah",
  36
+    javahOutputEnv := DefaultJavahOutputEnv,
  37
+    javahOutputFile := None
34 38
     ))
35 39
 
36 40
   // ndk-related paths
@@ -48,13 +52,75 @@ object AndroidNdk {
48 52
 	  } yield b
49 53
 	  paths.headOption getOrElse (sys.error("Android NDK not found.  " +
50 54
         "You might need to set " + envs.mkString(" or ")))
51  
-      }
  55
+      },
  56
+
  57
+    javahPath <<= (javaHome, javahName) apply { (home, name) =>
  58
+      home map ( h => (h / "bin" / name).absolutePath ) getOrElse name
  59
+    },
  60
+
  61
+    javahOutputDirectory <<= (sourceManaged)(_ / "main" / DefaultJniDirectoryName )
  62
+
52 63
   ))
53 64
 
  65
+  private def split(file: File) = {
  66
+    val parentsBottomToTop = Iterator.iterate(file)(_.getParentFile).takeWhile(_ != null).map(_.getName).toSeq
  67
+    parentsBottomToTop.reverse
  68
+  }
  69
+
  70
+  private def compose(parent: File, child: File): File = {
  71
+    if (child.isAbsolute) {
  72
+      child
  73
+    } else {
  74
+      split(child).foldLeft(parent)(new File(_,_))
  75
+    }
  76
+  }
  77
+
  78
+  private def javahTask(
  79
+    javahPath: String,
  80
+    classpath: Seq[File],
  81
+    classes: Seq[String],
  82
+    outputDirectory: File,
  83
+    outputFile: Option[File],
  84
+    streams: TaskStreams) {
  85
+
  86
+    val log = streams.log
  87
+    if (classes.isEmpty) {
  88
+      log.debug("No JNI classes, skipping javah")
  89
+    } else {
  90
+      outputDirectory.mkdirs()
  91
+      val classpathArgument = classpath.map(_.getAbsolutePath()).mkString(File.pathSeparator)
  92
+      val outputArguments = outputFile match {
  93
+        case Some(file) =>
  94
+          val outputFile = compose(outputDirectory, file)
  95
+          // Neither javah nor RichFile.relativeTo will work unless the directories exist.
  96
+          Option(outputFile.getParentFile) foreach (_.mkdirs())
  97
+          if (! (outputFile relativeTo outputDirectory).isDefined) {
  98
+            log.warn("javah output file [" + outputFile + "] is not within javah output directory [" +
  99
+                outputDirectory + "], continuing anyway")
  100
+          }
  101
+
  102
+          Seq("-o", outputFile.absolutePath)
  103
+        case None => Seq("-d", outputDirectory.absolutePath)
  104
+      }
  105
+      val javahCommandLine = Seq(
  106
+        javahPath,
  107
+        "-classpath", classpathArgument) ++
  108
+        outputArguments ++ classes
  109
+      log.debug("Running javah: " + (javahCommandLine mkString " "))
  110
+      val exitCode = Process(javahCommandLine) ! log
  111
+
  112
+      if (exitCode != 0) {
  113
+        sys.error("javah exited with " + exitCode)
  114
+      }
  115
+    }
  116
+  }
54 117
 
55 118
   private def ndkBuildTask(targets: String*) =
56  
-    (ndkBuildPath, nativeOutputPath) map { (ndkBuildPath, obj) =>
57  
-      val exitValue = Process(ndkBuildPath.absolutePath :: "-C" :: obj.absolutePath :: targets.toList) !
  119
+    (ndkBuildPath, javahOutputEnv, javahOutputDirectory, nativeOutputPath) map {
  120
+    (ndkBuildPath, javahOutputEnv, javahOutputDirectory, obj) =>
  121
+      val exitValue = Process(ndkBuildPath.absolutePath :: "-C" :: obj.absolutePath ::
  122
+          (javahOutputEnv + "=" + javahOutputDirectory.absolutePath) ::
  123
+          targets.toList) !
58 124
 
59 125
       if(exitValue != 0) sys.error("ndk-build failed with nonzero exit code (" + exitValue + ")")
60 126
 
@@ -62,11 +128,35 @@ object AndroidNdk {
62 128
     }
63 129
 
64 130
   lazy val settings: Seq[Setting[_]] = defaultSettings ++ pathSettings ++ inConfig(Android) (Seq (
  131
+    javah <<= (
  132
+        (compile in Compile),
  133
+        javahPath,
  134
+        (classDirectory in Compile), (internalDependencyClasspath in Compile), (externalDependencyClasspath in Compile),
  135
+        jniClasses,
  136
+        javahOutputDirectory, javahOutputFile,
  137
+        streams) map ((
  138
+            _, // we only depend on a side effect (built classes) of compile
  139
+            javahPath,
  140
+            classDirectory, internalDependencyClasspath, externalDependencyClasspath,
  141
+            jniClasses,
  142
+            javahOutputDirectory,
  143
+            javahOutputFile,
  144
+            streams) =>
  145
+        javahTask(
  146
+          javahPath,
  147
+          Seq(classDirectory) ++ internalDependencyClasspath.files ++ externalDependencyClasspath.files,
  148
+          jniClasses,
  149
+          javahOutputDirectory, javahOutputFile,
  150
+          streams)
  151
+    	),
65 152
     ndkBuild <<= ndkBuildTask(),
  153
+    ndkBuild <<= ndkBuild.dependsOn(javah),
66 154
     ndkClean <<= ndkBuildTask("clean"),
67  
-    (compile in Compile) <<= (ndkBuild in Android, compile in Compile) map { (ndkBuild, compile) => ndkBuild ; compile }
  155
+    jniClasses := Seq.empty,
  156
+    (products in Compile) <<= (products in Compile).dependsOn(ndkBuild),
  157
+    javahClean <<= (javahOutputDirectory) map IO.delete
68 158
   )) ++ Seq (
69 159
     cleanFiles <+= (nativeObjectPath in Android),
70  
-    clean <<= (clean, ndkClean in Android) map { (clean, ndkClean) => ndkClean ; clean  }
  160
+    clean <<= clean.dependsOn(ndkClean in Android, javahClean in Android)
71 161
   )
72 162
 }
14  src/main/scala/AndroidNdkKeys.scala
@@ -20,4 +20,18 @@ object AndroidNdkKeys {
20 20
   val ndkBuild = TaskKey[Unit]("ndk-build", "Compile native C/C++ sources.")
21 21
   val ndkClean = TaskKey[Unit]("ndk-clean", "Clean resources built from native C/C++ sources.")
22 22
 
  23
+  val javahName = SettingKey[String]("javah-name", "The name of the javah command for generating JNI headers")
  24
+  val javahPath = SettingKey[String]("javah-path", "The path to the javah executable")
  25
+  val javah = TaskKey[Unit]("javah", "Produce C headers from Java classes with native methods")
  26
+  val javahClean = TaskKey[Unit]("javah-clean", "Clean C headers built from Java classes with native methods")
  27
+
  28
+  val javahOutputDirectory = SettingKey[File]("javah-output-directory",
  29
+      "The directory where JNI headers are written to.")
  30
+  val javahOutputFile = SettingKey[Option[File]]("javah-output-file",
  31
+      "filename for the generated C header, relative to javah-output-directory")
  32
+  val javahOutputEnv = SettingKey[String]("javah-output-env",
  33
+      "Name of the make environment variable to bind to the javah-output-directory")
  34
+
  35
+  val jniClasses = SettingKey[Seq[String]]("jni-classes",
  36
+      "Fully qualified names of classes with native methods for which JNI headers are to be generated.")
23 37
 }

0 notes on commit 053ca2d

Please sign in to comment.
Something went wrong with that request. Please try again.