lørdag 8. september 2012

java.jar to .Net.dll with Gradle and IKVM

If you ever (for whatever reason) find your self in need of creating .Net DLLs from java jar files. Not to despair. IKVM might just be the tool you are looking for. Its pretty neat stuff and I was amazed how much that actually worked. Debugging into my javacode from Visual Studio .Net was rather impressive I have to admit.

 Now what if you need to integrate the generation of DLLs as part of your Gradle multi project ?  A little while back I was on a project that had this particular need so I thought I'd share some harvested knowledge.

I ended up solving this in a gradle script, but it would probably benefit from being packaged as a plugin. Anyways here goes:

I added the following to a ikvm.gradle

ext.executeIkvm = {ikvmDll, args ->
  def ikvmExec = new File(System.getenv()['IKVM_HOME'] , 'bin/ikvmc.exe')
  assert ikvmExec.exists(), "You must install Ikvm and set IKVM_HOME env variable"

  def commandLineArgs = [ikvmExec] + args
  println commandLineArgs
  exec {
    commandLine = commandLineArgs
  }
}

ext.addIkvmTask = {ikvmProject->
  if(ikvmProject.getTasksByName("ikvm", false)) return
  
  ikvmProject.task("ikvm", dependsOn: ikvmProject.jar) {
  description = "Generates Ikvm dll for given project"
  ext.ikvmOutDir = file("${ikvmProject.buildDir}/ikvm")
  ext.ikvmDll = new File(ikvmOutDir, "${ikvmProject.name}.dll")
  ext.referencedDlls = [] as Set

  inputs.files ikvmProject.jar.archivePath
  outputs.files ikvmOutDir

  doFirst {
    if (!ikvmOutDir.exists()) ikvmOutDir.mkdir()
  }

  doLast {
    def commandLineArgs = [
 "-out:$ikvmDll", "-version:$version", 
 ikvmProject.jar.archivePath] + referencedDlls.collect{"-reference:$it"}
      executeIkvm(ikvmDll, commandLineArgs)
    }
  }
  ikvmProject.build.dependsOn ikvmProject.ikvm
}

ext.configureIkvmProject = {ikvmProject ->
  addIkvmTask(ikvmProject)
    
  def runtimeDeps = ikvmProject.configurations.runtime.allDependencies
  def referencedSubDlls = runtimeDeps.collect {dependency ->
    if (dependency instanceof ProjectDependency) {
      def ikvmSubProject = project(":${dependency.name}")
      addIkvmTask(ikvmSubProject)
      ikvmProject.ikvm.dependsOn ikvmSubProject.ikvm
      return [ikvmSubProject.ikvm.ikvmDll] + configureIkvmProject(ikvmSubProject)
    } else {
      throw new RuntimeException("Sorry not supporting third party deeps")
    }
  }.flatten()
    
  ikvmProject.ikvm.referencedDlls += referencedSubDlls
}
(actually I added some more stuff to deal with the outputstream from exec, but it just muddles the implementation example above)


In my root build.gradle I added the following:

subprojects{
  //...
  apply from: "$rootDir/ikvm.gradle"
  afterEvaluate { project ->
    configureIkvmProject project
  }
  //...
}

Now you basically have the ability to generate a 1-1 mapping between your jar projects and .Net dlls. As far as I know there is no such thing as transitive dependencies in .Net, so we opted to add any transitives into the linking metainfo for the dlls.

Not shown, but we also added the ability to

  • generate dll's for transitive 3.rd party jar files 
  • the ability to generate .net debug info (pdb files)
  • checking in generated dlls to VCS to trigger .Net build on TeamCity
  • convenience tasks for copying generated artifacts to lib folder in .Net project(s)