├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala ├── ClojureC.scala ├── ClojurePlugin.scala └── Keys.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | 12 | # Scala-IDE specific 13 | .scala_dependencies 14 | 15 | .cache 16 | 17 | .classpath 18 | 19 | .project 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Geoffroy Couprie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sbt-clojure 2 | =========== 3 | 4 | an sbt plugin for clojure, inspired from [sbt-groovy](https://github.com/fupelaqu/sbt-groovy). 5 | 6 | To use Clojure code in your Scala project, follow these few steps: 7 | 8 | 1. add this to build.sbt 9 | 10 | ``` 11 | seq(clojure.settings :_*) 12 | 13 | libraryDependencies += "org.clojure" % "clojure" % "1.5.1" 14 | ``` 15 | 16 | 2. add this to projectt/plugins.sbt 17 | 18 | ``` 19 | addSbtPlugin("com.unhandledexpression" % "sbt-clojure" % "0.1") 20 | ``` 21 | 22 | 3. Create a clojure directory to hold the sources 23 | 24 | Example: 25 | 26 | ``` 27 | src/ 28 | └── main 29 | ├── clojure 30 | │   ├── hello.clj 31 | │   └── sub 32 | │   └── num.clj 33 | └── scala 34 | └── main.scala 35 | ``` 36 | 37 | An [example project](https://github.com/Geal/sbt-clojure-example) is available for reference. 38 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | sbtPlugin := true 2 | 3 | name := "sbt-clojure" 4 | 5 | organization := "com.unhandledexpression" 6 | 7 | version := "0.1" 8 | 9 | scalaVersion := "2.10.2" 10 | 11 | publishMavenStyle := true 12 | 13 | publishTo := { 14 | val nexus = "https://oss.sonatype.org/" 15 | if (version.value.trim.endsWith("SNAPSHOT")) 16 | Some("snapshots" at nexus + "content/repositories/snapshots") 17 | else 18 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 19 | } 20 | 21 | libraryDependencies += "org.clojure" % "clojure" % "1.5.1" 22 | 23 | publishArtifact in Test := false 24 | 25 | pomIncludeRepository := { _ => false } 26 | 27 | pomExtra := ( 28 | https://github.com/Geal/sbt-clojure 29 | 30 | 31 | MIT 32 | http://opensource.org/licenses/mit-license.php 33 | repo 34 | 35 | 36 | 37 | git@github.com:Geal/sbt-clojure.git 38 | scm:git:git@github.com:Geal/sbt-clojure.git 39 | 40 | 41 | 42 | gcouprie 43 | Geoffroy Couprie 44 | http://geoffroycouprie.com 45 | 46 | ) 47 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbtVersion=0.13.0 2 | 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.3.0") 2 | 3 | -------------------------------------------------------------------------------- /src/main/scala/ClojureC.scala: -------------------------------------------------------------------------------- 1 | package com.unhandledexpression.sbtclojure 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | import java.io.File 6 | 7 | import sbt.classpath.ClasspathUtilities 8 | 9 | class ClojureC(val classpath : Seq[File], val sourceDirectory : File, val stubDirectory : File, val destinationDirectory : File) { 10 | 11 | lazy val oldContextClassLoader = Thread.currentThread.getContextClassLoader 12 | 13 | lazy val classLoader = ClasspathUtilities.toLoader(classpath) 14 | lazy val clojureClass = classLoader.loadClass("org.clojure.core") 15 | lazy val rt = classLoader.loadClass("clojure.lang.RT") 16 | lazy val varClass = classLoader.loadClass("clojure.lang.Var") 17 | lazy val varFunction = rt.getDeclaredMethod("var", classOf[java.lang.String], classOf[java.lang.String]) 18 | lazy val loadResourceFunction = rt.getDeclaredMethod("loadResourceScript", classOf[java.lang.String]) 19 | lazy val rtLoadFunction = rt.getDeclaredMethod("load", classOf[java.lang.String]) 20 | lazy val rtInitFunction = rt.getDeclaredMethod("init") 21 | lazy val rtMap = rt.getDeclaredMethod("map", classOf[Array[Object]]) 22 | 23 | def getRecursiveListOfFiles(dir: File): Array[File] = { 24 | val files = dir.listFiles 25 | files ++ files.filter(_.isDirectory).flatMap(getRecursiveListOfFiles) 26 | } 27 | 28 | def compile() : Unit = { 29 | val files = getRecursiveListOfFiles(sourceDirectory) 30 | 31 | IO.createDirectory(sourceDirectory) 32 | IO.createDirectory(destinationDirectory) 33 | try{ 34 | Thread.currentThread().setContextClassLoader(classLoader) 35 | rtInitFunction.invoke(null) 36 | val compilerClass = classLoader.loadClass("clojure.lang.Compiler") 37 | val loadFunction = compilerClass.getDeclaredMethod("load", classOf[java.io.Reader]) 38 | val compileFunction = compilerClass.getDeclaredMethod("compile", classOf[java.io.Reader], classOf[java.lang.String], classOf[java.lang.String]) 39 | 40 | val associativeClass = classLoader.loadClass("clojure.lang.Associative") 41 | val pushTBFunction = varClass.getDeclaredMethod("pushThreadBindings", associativeClass) 42 | val popTBFunction = varClass.getDeclaredMethod("popThreadBindings") 43 | 44 | val compilePath = varFunction.invoke(null, "clojure.core", "*compile-path*") 45 | val compileFiles = varFunction.invoke(null, "clojure.core", "*compile-files*") 46 | 47 | val newMap = rtMap.invoke(null, Array(compilePath, destinationDirectory.getAbsolutePath(), compileFiles, true:java.lang.Boolean)) 48 | pushTBFunction.invoke(null, newMap) 49 | 50 | files.filter(!_.isDirectory).map { f => 51 | //val suffix = f.toString.stripPrefix(sourceDirectory.toString) 52 | compileFunction.invoke(null, new java.io.FileReader(f.getAbsolutePath()), f.getName(), f.getName()) 53 | } 54 | 55 | popTBFunction.invoke(null) 56 | } 57 | finally{ 58 | Thread.currentThread.setContextClassLoader(oldContextClassLoader) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/scala/ClojurePlugin.scala: -------------------------------------------------------------------------------- 1 | package com.unhandledexpression.sbtclojure 2 | 3 | import sbt._ 4 | import Keys._ 5 | import java.io.File 6 | 7 | object ClojurePlugin extends Plugin { 8 | 9 | private object ClojureDefaults extends Keys { 10 | val settings = Seq( 11 | clojureVersion := "1.5.1", 12 | libraryDependencies ++= Seq[ModuleID]( 13 | "org.clojure" % "clojure" % clojureVersion.value % Config.name 14 | ) 15 | ) 16 | } 17 | 18 | object clojure extends Keys { 19 | val settings = Seq(ivyConfigurations += Config) ++ ClojureDefaults.settings ++ Seq( 20 | clojureSource in Compile := (sourceDirectory in Compile).value / "clojure", 21 | unmanagedResourceDirectories in Compile += {(clojureSource in Compile).value}, 22 | clojurec in Compile := { 23 | val s: TaskStreams = streams.value 24 | val sourceDirectory : File = (clojureSource in Compile).value 25 | val nb = (sourceDirectory ** "*.clj").get.size 26 | if(nb > 0){ 27 | val s: TaskStreams = streams.value 28 | s.log.info("Start Compiling Clojure sources") 29 | val classpath : Seq[File] = update.value.select( configurationFilter(name = "*") ) ++ Seq((classDirectory in Compile).value) 30 | val stubDirectory : File = (sourceManaged in Compile).value 31 | val destinationDirectory : File = (classDirectory in Compile).value 32 | 33 | def clojureClazz(file : File) : File = { 34 | val p = file.getAbsolutePath() 35 | new File(destinationDirectory.getAbsolutePath() + p.substring(sourceDirectory.getAbsolutePath().length(), p.length() - ".clj".length()) + ".class") 36 | } 37 | 38 | (sourceDirectory ** "*.clj").get map (clojureClazz) foreach {f => if(f.exists()){IO.delete(f)}} 39 | 40 | new ClojureC(classpath, sourceDirectory, stubDirectory, destinationDirectory).compile 41 | } 42 | }, 43 | compile in Compile <<= (compile in Compile) dependsOn (clojurec in Compile) 44 | ) 45 | } 46 | 47 | object testClojure extends TestKeys { 48 | val settings = Seq(ivyConfigurations += Config) ++ inConfig(Config)(Defaults.testTasks ++ ClojureDefaults.settings ++ Seq( 49 | definedTests <<= definedTests in Test, 50 | definedTestNames <<= definedTestNames in Test, 51 | fullClasspath <<= fullClasspath in Test, 52 | 53 | clojureSource in Test := (sourceDirectory in Test).value / "clojure", 54 | unmanagedResourceDirectories in Test += {(clojureSource in Test).value}, 55 | clojurec in Test := { 56 | val sourceDirectory : File = (clojureSource in Test).value 57 | val nb = (sourceDirectory ** "*.clj").get.size 58 | if(nb > 0){ 59 | val s: TaskStreams = streams.value 60 | s.log.info("Start Compiling Test Clojure sources") 61 | val classpath : Seq[File] = update.value.select( configurationFilter(name = "*") ) ++ Seq((classDirectory in Test).value) ++ Seq((classDirectory in Compile).value) 62 | val stubDirectory : File = (sourceManaged in Test).value 63 | val destinationDirectory : File = (classDirectory in Test).value 64 | 65 | def clojureClazz(file : File) : File = { 66 | val p = file.getAbsolutePath() 67 | new File(destinationDirectory.getAbsolutePath() + p.substring(sourceDirectory.getAbsolutePath().length(), p.length() - ".clj".length()) + ".class") 68 | } 69 | 70 | (sourceDirectory ** "*.clj").get map (clojureClazz) foreach {f => if(f.exists()){IO.delete(f)}} 71 | 72 | new ClojureC(classpath, sourceDirectory, stubDirectory, destinationDirectory).compile 73 | } 74 | }, 75 | clojurec in Test <<= (clojurec in Test) dependsOn (compile in Test), 76 | test in Test <<= (test in Test) dependsOn (clojurec in Test) 77 | )) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/Keys.scala: -------------------------------------------------------------------------------- 1 | package com.unhandledexpression.sbtclojure 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | import java.io.File 6 | 7 | trait Keys { 8 | 9 | lazy val Config = config("clojure") extend(Compile) hide 10 | lazy val clojureVersion = settingKey[String]("Clojure version") 11 | lazy val clojureSource = settingKey[File]("Default Clojure source directory") 12 | lazy val clojurec = taskKey[Unit]("Compile Clojure sources") 13 | 14 | } 15 | 16 | trait TestKeys extends Keys { 17 | override lazy val Config = config("test-clojure") extend(Test) hide 18 | } 19 | 20 | trait IntegrationTestKeys extends TestKeys { 21 | override lazy val Config = config("it-clojure") extend(IntegrationTest) hide 22 | } 23 | --------------------------------------------------------------------------------