Table of Contents
If you’re ever working on a really small Scala project — something that contains only a few source code files — and don’t want to use sbt to create a JAR file, you can do it yourself manually. Let’s look at a quick example. Note that the commands below work on Mac and Linux systems, and should work on Windows with minor changes.
Example
Given a file named Hello.scala with these contents:
object Hello extends App { println("Hello, world") }
you can compile that source code file directly to a JAR file like this:
$ scalac Hello.scala -d Hello.jar
That creates Hello.jar, whose contents you can look at like this:
$ jar tvf Hello.jar 76 Mon Apr 15 18:45:56 MDT 2019 META-INF/MANIFEST.MF 2554 Mon Apr 15 18:45:56 MDT 2019 Hello$.class 753 Mon Apr 15 18:45:56 MDT 2019 Hello.class 751 Mon Apr 15 18:45:56 MDT 2019 Hello$delayedInit$body.class
The META-INF/MANIFEST.MF file contains these contents:
Manifest-Version: 1.0 Scala-Compiler-Version: 2.12.8 Main-Class: Hello
Now you can run/execute the JAR file with the scala
command like this:
$ scala Hello.jar
That produces the expected output:
Hello, world
That’s all there is to it. The key to this solution is knowing that the scalac
“-d” option can be used to create an executable JAR fie.
A more complex example
As a more complicated example I just did a similar thing by creating a one-object application that relies on two JAR files, i.e., two dependencies. The commands I used are the same, except for adding in the classpath complexity. Assuming that I start with a Scala source code file named Foo.scala and want to create a JAR file named Foo.jar, the scalac
command to create the JAR file looks like this:
$ scalac -classpath "sed_2.12-0.1.jar:kaleidoscope_2.12-0.1.0.jar" Foo.scala -d Foo.jar
I can then run the application with the scala
command like this:
$ scala -classpath "sed_2.12-0.1.jar:kaleidoscope_2.12-0.1.0.jar:Foo.jar" Foo
I should mention that the Foo.scala file has source code that begins like this:
object Foo extends App ...
It’s not in a package, so I refer to Foo
at the end of the scala
command (as opposed to something like my.package.Foo
).
Problems?
A few people wrote to say that this technique didn’t work for them. It worked on several examples for me, and today (May 12, 2019) I had my first problem with it. My problem seems to be that it generated an empty META-INF/MANIFEST.MF file. I tried a couple of things to get this to work, but I’m out of time for today; I’ll work on it again when I have more time.
Another thing I just noticed (in July, 2019) is that another JAR file didn’t run correctly. When I tried to run it I got this error:
$ scala RenumberAllMdFiles.jar RenumberAllMdFiles java.lang.NullPointerException at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at scala.reflect.internal.util.ScalaClassLoader.$anonfun$tryClass$1(ScalaClassLoader.scala:52) at scala.util.control.Exception$Catch.$anonfun$opt$1(Exception.scala:246) at scala.util.control.Exception$Catch.apply(Exception.scala:228) at scala.util.control.Exception$Catch.opt(Exception.scala:246) at scala.reflect.internal.util.ScalaClassLoader.tryClass(ScalaClassLoader.scala:52) at scala.reflect.internal.util.ScalaClassLoader.tryToInitializeClass(ScalaClassLoader.scala:48) at scala.reflect.internal.util.ScalaClassLoader.tryToInitializeClass$(ScalaClassLoader.scala:48) at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.tryToInitializeClass(ScalaClassLoader.scala:132) at scala.reflect.internal.util.ScalaClassLoader.run(ScalaClassLoader.scala:99) at scala.reflect.internal.util.ScalaClassLoader.run$(ScalaClassLoader.scala:98) at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:132) at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:28) at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:27) at scala.tools.nsc.JarRunner$.run(MainGenericRunner.scala:21) at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:35) at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:34) at scala.tools.nsc.JarRunner$.runAndCatch(MainGenericRunner.scala:21) at scala.tools.nsc.JarRunner$.runJar(MainGenericRunner.scala:33) at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:79) at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:92) at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:103) at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:108) at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
I recognized that error message, and knew that I probably needed to add the scala-library.jar file to my classpath, so I ran the command again like this and it worked fine:
$ scala -cp $SCALA_HOME/lib/scala-library.jar:RenumberAllMdFiles.jar RenumberAllMdFiles
At the moment I can’t remember why that’s required, but I can tell you that it fixes the problem.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Summary
If you ever need to create a JAR file with scalac
and then run that JAR file with scala
, I hope these examples are helpful.