Ich bin neu bei Scala und kenne Java nicht. Ich möchte eine Jar-Datei aus einer einfachen Scala-Datei erstellen. Also ich habe meine HelloWorld.scala, generiere eine HelloWorld.jar.
Manifest.mf:
Main-Class: HelloWorld
In der Konsole führe ich aus:
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)
Beispielverzeichnisstruktur:
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
Um den Schalter - jar erfolgreich zu verwenden, benötigen Sie zwei Einträge in der Datei META-INF/MANIFEST.MF : the Hauptklasse; relative URLs zu Abhängigkeiten. Die Dokumentation stellt fest:
- Glas
Führen Sie ein Programm aus, das in einer JAR-Datei enthalten ist. Das erste Argument ist der Name einer JAR-Datei anstelle eines Startklassennamens. Damit diese Option funktioniert, muss das Manifest der JAR-Datei eine Zeile der Form Hauptklasse: Klassenname enthalten. Hier kennzeichnet Klassenname die Klasse mit der öffentlichen statischen Methode void main (String [] args), die als Ausgangspunkt für Ihre Anwendung dient. Weitere Informationen zum Arbeiten mit Jar-Dateien und Jar-Dateimanifesten finden Sie auf der Jar-Tool-Referenzseite und im Jar-Trail des Lernprogramms Java.
Wenn Sie diese Option verwenden, ist die JAR-Datei die Quelle aller Benutzerklassen. und andere Benutzerklassenpfadeinstellungen werden ignoriert.
(Anmerkungen: JAR-Dateien können mit den meisten Zip-Anwendungen überprüft werden. Ich vernachlässige wahrscheinlich die Behandlung von Leerzeichen in Verzeichnisnamen im Stapelskript. Scala code runner version 2.7.4.final.)
Der Vollständigkeit halber ein gleichwertiges Bash-Skript:
#!/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
Da für Scala-Skripte die Installation der Scala-Bibliotheken erforderlich ist, müssen Sie die Scala-Laufzeitumgebung in Ihr JAR aufnehmen.
Dazu gibt es viele Strategien, z. B. jar jar . Das letzte Problem ist jedoch, dass der von Ihnen gestartete Java-Prozess die Scala-JARs nicht finden kann.
Für ein einfaches Stand-Alone-Skript würde ich die Verwendung von jar jar empfehlen. Andernfalls sollten Sie ein Abhängigkeits-Management-Tool suchen oder die Installation von Scala im JDK erfordern.
Am Ende habe ich sbt Assembly benutzt, es ist wirklich einfach zu benutzen. Ich habe eine Datei namens Assembly.sbt
im project/
-Verzeichnis im Stammverzeichnis des Projekts mit einem Einzeiler hinzugefügt (Beachten Sie, dass Ihre Version möglicherweise geändert werden muss).
addSbtPlugin("com.eed3si9n" % "sbt-Assembly" % "0.11.2")
Dann führen Sie einfach die Assembly
-Task in sbt
aus:
> Assembly
Oder einfach 'sbt Assembly' im Projektstammverzeichnis
$ sbt Assembly
Es führt zuerst Ihre Tests aus und generiert dann die neue jar im target/
-Verzeichnis (vorausgesetzt, mein build.sbt
listet bereits alle meine Abhängigkeiten auf).
In meinem Fall mache ich die .jar
-Datei einfach zu einer ausführbaren Datei, benenne sie um, um die Erweiterung zu entfernen, und sie ist versandfertig!
Wenn Sie ein Befehlszeilentool ausführen, vergessen Sie nicht, eine man-Seite hinzuzufügen (Ich hasse Skripts ohne richtige Manpages oder mit mehrseitiger Klartextdokumentation, die für Sie nicht in einen Pager geleitet wird.) .
Sie können auch Maven und das Maven-Scala-Plugin verwenden. Wenn Sie maven eingerichtet haben, können Sie einfach mvn package machen und es wird Ihr Jar für Sie erstellen.
Ich habe versucht, die Methode von MyDowell zu reproduzieren. Endlich konnte ich es schaffen. Allerdings finde ich, dass die Antwort für einen Anfänger zwar zu kompliziert ist (insbesondere die Verzeichnisstruktur ist unnötig kompliziert).
Ich kann dieses Ergebnis mit sehr einfachen Mitteln reproduzieren. Zunächst gibt es nur ein Verzeichnis, das drei Dateien enthält:
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
erstes Kompilieren von helloworld.scala:
scalac helloworld.scala
dann erstelle das Glas:
\progra~1\Java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF .
jetzt können Sie es ausführen mit:
Java -jar helloworld.jar
Ich habe diese einfache Lösung gefunden, weil die ursprüngliche nicht funktioniert hat. Später habe ich herausgefunden, dass es nicht falsch ist, sondern aufgrund eines trivialen Fehlers: Wenn ich die zweite Zeile in MANIFEST.MF nicht mit einem Zeilenumbruch schließe, wird diese Zeile ignoriert. Ich brauchte eine Stunde, um das herauszufinden, und ich habe alle anderen Dinge zuvor ausprobiert. Dabei fand ich diese sehr einfache Lösung.
Ich möchte nicht schreiben, warum und wie, sondern nur die Lösung zeigen, die in meinem Fall funktionierte (über Linux Ubuntu-Befehlszeile):
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
Eine Sache, die ein ähnliches Problem verursachen kann (obwohl dies nicht das Problem in der ursprünglichen Frage oben ist) ist, dass der Java-VM scheinbar verlangt, dass die Hauptmethode void
zurückgibt. In Scala können wir etwas schreiben ( Beachten Sie das = -Zeichen in der Definition von main ):
object MainProgram {
def main(args: Array[String]) = {
new GUI(args)
}
}
dabei gibt main tatsächlich ein GUI
- Objekt zurück (d. h. es ist nicht void
), aber das Programm wird gut ausgeführt, wenn wir es mit dem Befehl scala starten.
Wenn wir diesen Code in eine jar-Datei mit MainProgram
als Main-Klasse packen, wird der Java-VM-Server sich darüber beschweren, dass es keine Hauptfunktion gibt, da der Rückgabetyp unseres main nicht void
ist (ich finde diese Beschwerde etwas seltsam, da die Rückgabetyp ist nicht Teil der Signatur).
Wir hätten keine Probleme, wenn wir das = -sign im Header von main weglassen oder es explizit als Unit
deklarieren.
Ich habe das Bash-Skript geändert und einige Intelligenz hinzugefügt, einschließlich der Erstellung von automatischen Manifestationen.
Bei diesem Skript wird davon ausgegangen, dass das Hauptobjekt genauso benannt ist wie die Datei, in der es sich befindet (case sensitive). Außerdem muss entweder der aktuelle Verzeichnisname dem Hauptobjektnamen entsprechen oder der Hauptobjektname sollte als Befehlszeilenparameter angegeben werden. Starten Sie dieses Skript im Stammverzeichnis Ihres Projekts. Ändern Sie die Variablen oben nach Bedarf.
Beachten Sie, dass das Skript die Ordner bin und dist generiert und alle vorhandenen Inhalte in bin löscht.
#!/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 " "
Wenn Sie keine sbt-Funktionen verwenden möchten, empfehle ich die Verwendung eines Makefiles.
Hier ist ein Beispiel, in dem foo package zur Vollständigkeit durch foo.bar.myApp ersetzt wird.
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)" > [email protected]
@echo "Class-Path: scala-library.jar" >> [email protected]
$(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)[email protected]
.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