質問
私はScalaを初めて使い、Javaを知りません。単純なScalaファイルから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 ファイルに2つのエントリが必要です。メインクラス。依存関係への相対URL。ドキュメントノート:
-jar
にカプセル化されたプログラムを実行する JARファイル。最初の引数は 代わりにJARファイルの名前 起動クラス名。これのために 働くオプション、のマニフェスト JARファイルには、 フォームMain-Class:クラス名。ここに、 classnameは次のクラスを識別します public static void main(String [] args)として機能するメソッド アプリケーションの開始点。をご覧ください JarツールのリファレンスページとJar Javaチュートリアルの軌跡 Jarの使用に関する情報 ファイルとjarファイルマニフェスト。
このオプションを使用すると、JARファイル すべてのユーザークラスのソースです。 およびその他のユーザークラスパス設定は無視されます。
(注:JARファイルはほとんどのZIPアプリケーションで検査できます。おそらく、バッチスクリプト内のディレクトリ名のスペースの処理は無視します。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を見つけられないことです。
単純なスタンドアロンスクリプトの場合、jar jarを使用することをお勧めします。それ以外の場合は、依存関係管理ツールを確認するか、ユーザーにScalaを JDKにインストールするように要求する
> sbtアセンブリを使用することになりました。これは本当に簡単です。プロジェクトのルートにあるassembly.sbt
ディレクトリに、ライナーを1つ付けてproject/
というファイルを追加しました(バージョンを変更する必要がある場合があります)。
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
次に、assembly
でsbt
タスクを実行します:
> assembly
またはプロジェクトのルートディレクトリにある「sbt assembly」
$ sbt assembly
最初にテストを実行してから、新しいjarをtarget/
ディレクトリに生成します(私のbuild.sbt
がすべての依存関係を既にリストしている場合)。
私の場合、その.jar
ファイルを実行可能にし、拡張子を削除するように名前を変更するだけで、出荷する準備ができました!
また、コマンドラインツールを使用している場合は、を追加することを忘れないでくださいマニュアルページ(適切なマンページのないスクリプト、またはページャーにパイプでさえ送られない複数ページのプレーンテキストドキュメントがあるスクリプトは嫌いです。)
mavenおよびmaven-scala-pluginも使用できます。 mavenをセットアップしたら、mvnパッケージを実行するだけで、jarが作成されます。
MyDowellの方法を再現しようとしました。最後に、私はそれを機能させることができました。しかし、私は答えが初心者にとっては少し複雑すぎますが(特にディレクトリ構造は不必要に複雑です)、答えを見つけました。
非常に単純な手段でこの結果を再現できます。まず、3つのファイルを含むディレクトリが1つだけあります。
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
次に、jarを作成します:
\progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF .
次のコマンドで実行できます:
java -jar helloworld.jar
元のソリューションが機能しなかったため、この簡単なソリューションを見つけました。後でそれが間違っているからではなく、些細なエラーが原因であることがわかりました。MANIFEST.MFの2行目を改行で閉じないと、この行は無視されます。これを見つけるのに1時間かかり、この非常に簡単な解決策を見つける過程で、他のすべてを試しました。
理由と方法を書くのではなく、私のケースで機能するソリューションを(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スクリプトを変更して、自動マニフェスト生成などのインテリジェンスを追加しました。
このスクリプトは、メインオブジェクトの名前がその中のファイルと同じであると仮定しています(大文字と小文字が区別されます)。また、現在のディレクトリ名はメインオブジェクト名と同じであるか、コマンドラインパラメータとしてメインオブジェクト名を指定する必要があります。プロジェクトのルートディレクトリからこのスクリプトを起動します。必要に応じて上部の変数を変更します。
スクリプトが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 " "
同様の問題を引き起こす可能性のあることの1つは(上記の最初の質問の問題ではありませんが)、Java vmはmainメソッドがvoid
を返すことを要求しているように見えることです。 Scalaでは、次のように書くことができます( mainの定義で=記号を観察する):
object MainProgram {
def main(args: Array[String]) = {
new GUI(args)
}
}
mainは実際にGUI
オブジェクトを返します(つまり、MainProgram
ではありません)が、scalaコマンドを使用して起動すると、プログラムは正常に実行されます。
このコードをメインファイルとしてUnit
を使用してjarファイルにパッケージ化すると、メインの戻り値の型が<=>ではないため、Java vmはメイン関数がないと文句を言います(戻り値の型は署名の一部ではないため、この苦情はやや奇妙です。
mainのヘッダーで=記号を省略した場合、または<=>として明示的に宣言した場合は問題ありません。
sbt機能を使用したくない場合は、メイクファイルの使用をお勧めします。
完全性のために、 foo パッケージを foo.bar.myApp に置き換えた例を次に示します。
makefile
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