从Scala文件创build一个jar文件

我是新来的Scala,不知道Java。 我想从一个简单的Scala文件中创build一个jar文件。 所以我有我的HelloWorld.scala,生成一个HelloWorld.jar。

MANIFEST.MF:

Main-Class: HelloWorld 

在控制台中我运行:

 fsc HelloWorld.scala jar -cvfm HelloWorld.jar Manifest.mf HelloWorld\$.class HelloWorld.class java -jar HelloWorld.jar => "Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld/jar" java -cp HelloWorld.jar HelloWorld => Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:675) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124) at java.net.URLClassLoader.defineClass(URLClassLoader.java:260) at java.net.URLClassLoader.access$100(URLClassLoader.java:56) at java.net.URLClassLoader$1.run(URLClassLoader.java:195) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:316) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:280) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:374) at hoppity.main(HelloWorld.scala) 

示例目录结构:

 X:\scala\bin X:\scala\build.bat X:\scala\MANIFEST.MF X:\scala\src X:\scala\src\foo X:\scala\src\foo\HelloWorld.scala 

HelloWorld.scala:

 //file: foo/HelloWorld.scala package foo { object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } } } 

MANIFEST.MF:

 Main-Class: foo.HelloWorld Class-Path: scala-library.jar 

运行build.bat:

 @ECHO OFF IF EXIST hellow.jar DEL hellow.jar IF NOT EXIST scala-library.jar COPY %SCALA_HOME%\lib\scala-library.jar . CALL scalac -sourcepath src -d bin src\foo\HelloWorld.scala CD bin jar -cfm ..\hellow.jar ..\MANIFEST.MF *.* CD .. java -jar hellow.jar 

为了成功使用-jar开关,您需要META-INF / MANIFEST.MF文件中的两个条目:主类; 相对于任何依赖关系的URL。 文档说明:

-jar

执行封装在JAR文件中的程序。 第一个参数是JAR文件的名称,而不是启动类名称。 为了使这个选项起作用,JAR文件的清单必须包含一个Main-Class:classnameforms的行。 在这里,classname标识具有公共静态void main(String [] args)方法的类,该方法用作应用程序的起点。 有关使用Jar文件和Jar文件清单的信息,请参阅Jar工具参考页和Java Tutorial的Jar trail。

使用此选项时,JAR文件是所有用户类的来源, 而其他用户类path设置将被忽略。

  • java命令行用法
  • 清单规格

(注意:大多数ZIP应用程序都可以检查JAR文件;我可能忽略处理批处理脚本中目录名称中的空格; Scala代码运行器版本为2.7.4.final。)


为了完整,一个等效的bash脚本:

 #!/bin/bash if [ ! $SCALA_HOME ] then echo ERROR: set a SCALA_HOME environment variable exit fi if [ ! -f scala-library.jar ] then cp $SCALA_HOME/lib/scala-library.jar . fi scalac -sourcepath src -d bin src/foo/HelloWorld.scala cd bin jar -cfm ../hellow.jar ../MANIFEST.MF * cd .. java -jar hellow.jar 

由于Scala脚本需要安装Scala库,因此您必须将Scala运行时与您的JAR一起包含在内。

有很多策略可以做到这一点,比如jar jar ,但是最终你所看到的问题是你开始的Java进程找不到Scala JAR。

对于一个简单的独立脚本,我build议使用jar jar,否则你应该开始查看依赖pipe理工具,或者要求用户在JDK中安装Scala。

你也可以使用maven和maven-scala插件。 一旦你设置了maven,你可以做mvn包,它会为你创build你的jar。

我结束了使用sbt大会 ,这是非常简单的使用。 我用一行代码将一个名为assembly.sbt的文件添加到project/根目录下的project/目录中(注意您的版本可能需要更改)。

 addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") 

然后在sbt运行assembly任务:

 > assembly 

它会首先运行你的testing,然后它会生成新的jar到target/目录(假定我的build.sbt已经列出了我所有的依赖)。

在我的情况下,我只是使该.jar文件的可执行文件,重命名为删除扩展名,它已准备好出货!

另外,如果你正在做一个命令行工具,不要忘记添加一个手册页 (我讨厌没有合适的手册的脚本或者多页面的纯文本文档,甚至没有被传送到你的分页器中)。

我不想写下为什么,而只是显示在我的情况下工作的解决scheme(通过Linux的Ubuntu命令行):

1)

 mkdir scala-jar-example cd scala-jar-example 

2)

 nano Hello.scala object Hello extends App { println("Hello, world") } 

3)

 nano build.sbt import AssemblyKeys._ assemblySettings name := "MyProject" version := "1.0" scalaVersion := "2.11.0" 

3)

 mkdir project cd project nano plugins.sbt addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.9.1") 

4)

 cd ../ sbt assembly 

5)

 java -jar target/target/scala-2.11/MyProject-assembly-1.0.jar >> Hello, world 

我修改了bash脚本,添加了一些智能,包括自动清单生成。

此脚本假定主对象的名称与所在文件的名称相同(区分大小写)。 此外,当前目录名称必须等于主要对象名称,或者主要对象名称应作为命令行参数提供。 从你的项目的根目录启动这个脚本。 根据需要修改顶部的variables。

请注意,该脚本将生成bin和dist文件夹,并将删除bin中的任何现有内容。

 #!/bin/bash SC_DIST_PATH=dist SC_SRC_PATH=src SC_BIN_PATH=bin SC_INCLUDE_LIB_JAR=scala-library.jar SC_MANIFEST_PATH=MANIFEST.MF SC_STARTING_PATH=$(pwd) if [[ ! $SCALA_HOME ]] ; then echo "ERROR: set a SCALA_HOME environment variable" exit 1 fi if [[ ! -f $SCALA_HOME/lib/$SC_INCLUDE_LIB_JAR ]] ; then echo "ERROR: Cannot find Scala Libraries!" exit 1 fi if [[ -z "$1" ]] ; then SC_APP=$(basename $SC_STARTING_PATH) else SC_APP=$1 fi [[ ! -d $SC_DIST_PATH ]] && mkdir $SC_DIST_PATH if [[ ! -d $SC_BIN_PATH ]] ; then mkdir "$SC_BIN_PATH" else rm -r "$SC_BIN_PATH" if [[ -d $SC_BIN_PATH ]] ; then echo "ERROR: Cannot remove temp compile directory: $SC_BIN_PATH" exit 1 fi mkdir "$SC_BIN_PATH" fi if [[ ! -d $SC_SRC_PATH ]] || [[ ! -d $SC_DIST_PATH ]] || [[ ! -d $SC_BIN_PATH ]] ; then echo "ERROR: Directory not found!: $SC_SRC_PATH or $SC_DIST_PATH or $SC_BIN_PATH" exit 1 fi if [[ ! -f $SC_DIST_PATH/$SC_INCLUDE_LIB_JAR ]] ; then cp "$SCALA_HOME/lib/$SC_INCLUDE_LIB_JAR" "$SC_DIST_PATH" fi SCALA_MAIN=$(find ./$SC_SRC_PATH -name "$SC_APP.scala") COMPILE_STATUS=$? SCALA_MAIN_COUNT=$(echo "$SCALA_MAIN" | wc -l) if [[ $SCALA_MAIN_COUNT != "1" ]] || [[ ! $COMPILE_STATUS == 0 ]] ; then echo "Main source file not found or too many exist!: $SC_APP.scala" exit 1 fi if [[ -f $SC_DIST_PATH/$SC_APP.jar ]] ; then rm "$SC_DIST_PATH/$SC_APP.jar" if [[ -f $SC_DIST_PATH/$SC_APP.jar ]] ; then echo "Unable to remove existing distribution!: $SC_DIST_PATH/$SC_APP.jar" exit 1 fi fi if [[ ! -f $SC_MANIFEST_PATH ]] ; then LEN_BASE=$(echo $(( $(echo "./$SC_SRC_PATH" |wc -c) - 0 ))) SC_MAIN_CLASS=$(echo $SCALA_MAIN |cut --complement -c1-$LEN_BASE) SC_MAIN_CLASS=${SC_MAIN_CLASS%%.*} SC_MAIN_CLASS=$(echo $SC_MAIN_CLASS |awk '{gsub( "/", "'"."'"); print}') echo $(echo "Main-Class: "$SC_MAIN_CLASS) > $SC_MANIFEST_PATH echo $(echo "Class-Path: "$SC_INCLUDE_LIB_JAR) >> $SC_MANIFEST_PATH fi scalac -sourcepath $SC_SRC_PATH -d $SC_BIN_PATH $SCALA_MAIN COMPILE_STATUS=$? if [[ $COMPILE_STATUS != "0" ]] ; then echo "Compile Failed!" exit 1 fi cd "$SC_BIN_PATH" jar -cfm ../$SC_DIST_PATH/$SC_APP.jar ../$SC_MANIFEST_PATH * COMPILE_STATUS=$? cd "$SC_STARTING_PATH" if [[ $COMPILE_STATUS != "0" ]] || [[ ! -f $SC_DIST_PATH/$SC_APP.jar ]] ; then echo "JAR Build Failed!" exit 1 fi echo " " echo "BUILD COMPLETE!... TO LAUNCH: java -jar $SC_DIST_PATH/$SC_APP.jar" echo " " 

有一件事情可能会导致类似的问题(尽pipe上面的问题不是这个问题),Java虚拟机似乎要求主方法返回void 。 在Scala中我们可以写类似的东西( 在main的定义中注意= -sign ):

 object MainProgram { def main(args: Array[String]) = { new GUI(args) } } 

其中main实际上返回一个GUI (即它不是void ),但是当我们使用scala命令启动它时,程序将很好地运行。

如果我们将这个代码打包成一个jar文件, MainProgram作为Main-Class,那么Java虚拟机会抱怨没有main函数,因为main函数的返回types并不是void (我觉得这个抱怨有些奇怪,返回types不是签名的一部分)。

如果我们在main的头部忽略了= -sign,或者我们明确地声明了它为Unit ,那么我们就没有问题了。

我试图重现MyDowell的方法。 最后我可以使它工作。 不过,我发现答案虽然对新手来说有些复杂(特别是目录结构不必要的复杂)。

我可以用非常简单的方法重现这个结果。 首先只有一个包含三个文件的目录:

 helloworld.scala MANIFEST.MF scala-library.jar 

helloworld.scala

 object HelloWorld { def main(args: Array[String]) { println("Hello, world!") } } 

MANIFEST.MF:

 Main-Class: HelloWorld Class-Path: scala-library.jar 

首先编译helloworld.scala:

 scalac helloworld.scala 

然后创buildjar:

 \progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF . 

现在你可以运行它:

 java -jar helloworld.jar 

我发现这个简单的解决scheme,因为原来的一个没有工作。 后来我发现不是因为它是错误的,而是因为一个微不足道的错误:如果我不用一个换行符closuresMANIFEST.MF中的第二行,那么这行就会被忽略。 这花了我一个小时才发现,而且我尝试了所有其他的事情,在这个过程中find了这个非常简单的解决scheme。

如果你不想使用sbt设施,我build议使用makefile。

下面是一个例子,其中foo包被foo.bar.myApp取代以保证完整性。

生成文件

 NAME=HelloWorld JARNAME=helloworld PACKAGE=foo.bar.myApp PATHPACK=$(subst .,/,$(PACKAGE)) .DUMMY: default default: $(NAME) .DUMMY: help help: @echo "make [$(NAME)]" @echo "make [jar|runJar]" @echo "make [clean|distClean|cleanAllJars|cleanScalaJar|cleanAppJar]" .PRECIOUS: bin/$(PATHPACK)/%.class bin/$(PATHPACK)/%.class: src/$(PATHPACK)/%.scala scalac -sourcepath src -d bin $< scala-library.jar: cp $(SCALA_HOME)/lib/scala-library.jar . .DUMMY: runjar runJar: jar java -jar $(JARNAME).jar .DUMMY: jar jar: $(JARNAME).jar MANIFEST.MF: @echo "Main-Class: $(PACKAGE).$(NAME)" > $@ @echo "Class-Path: scala-library.jar" >> $@ $(JARNAME).jar: scala-library.jar bin/$(PATHPACK)/$(NAME).class \ MANIFEST.MF (cd bin && jar -cfm ../$(JARNAME).jar ../MANIFEST.MF *) %: bin/$(PATHPACK)/%.class scala -cp bin $(PACKAGE).$@ .DUMMY: clean clean: rm -R -f bin/* MANIFEST.MF cleanAppJar: rm -f $(JARNAME).jar cleanScalaJar: rm -f scala-library.jar cleanAllJars: cleanAppJar cleanScalaJar distClean cleanDist: clean cleanAllJars