2009-04-30 5 views
46

Ich bin neu in Scala und kenne Java nicht. Ich möchte eine JAR-Datei aus einer einfachen Scala-Datei erstellen. Also ich habe meine HelloWorld.scala, erzeuge eine HelloWorld.jar.Erstellen einer JAR-Datei aus einer Scala-Datei

Manifest.mf:

Main-Class: HelloWorld 

in der Konsole Ich betreibe:

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) 
+1

Haben erinnern Sie ein Manifest in der .jar enthalten? – millimoose

+0

Welche Klasse wird nicht gefunden? –

+0

Haben Sie Scala auch zuerst in Bytecode kompiliert? – millimoose

Antwort

53

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 die -jar Schalter erfolgreich zu verwenden, müssen Sie zwei Einträge in der META-INF/MANIFEST.MF-Datei: die Hauptklasse; relative URLs zu Abhängigkeiten. Die Dokumentation Hinweise:

-jar

Execute a program encapsulated in a JAR file. The first argument is the name of a JAR file instead of a startup class name. In order for this option to work, the manifest of the JAR file must contain a line of the form Main-Class: classname. Here, classname identifies the class having the public static void main(String[] args) method that serves as your application's starting point. See the Jar tool reference page and the Jar trail of the Java Tutorial for information about working with Jar files and Jar-file manifests.

When you use this option, the JAR file is the source of all user classes, and other user class path settings are ignored.

(Anmerkungen: JAR-Dateien können mit den meisten ZIP-Anwendungen überprüft werden; ich wahrscheinlich Vernachlässigung Umgang mit Leerzeichen im Verzeichnisnamen im Batch-Skript ; Scala Code Runner Version 2.7.4.final.)


Für Vollständigkeit, ein äquivalenter 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 
+0

Ich folgte diesen Anweisungen auf das Schreiben (das Kopieren und Einfügen und der Einrichtung der Verzeichnisse sind Sie vorschlagen) und ich bekomme die Fehlermeldung: java -jar Exception in thread "main" java.lang.NoClassDefFoundError hellow.jar: scala/ScalaObject \t bei java.lang.ClassLoader.defineClass1 (Mutter Methode) \t bei java.lang.ClassLoader.defineClass (KlassenLoader.Java: 676) \t bei java.security.SecureClassLoader.defineClass (SecureClassLoader.java:124) \t bei java.net.URLClassLoader.defineClass (URLClassLoader.java:260) . Baumstruktur: http://pastebin.com/3k8rCpxf – I82Much

+0

@ I82Much - Ich sehe keine grellen Probleme mit Ihrer Baumstruktur; Ich würde hellow.jar aufheben und überprüfen, ob die Manifestdatei die richtige Abhängigkeit hat. – McDowell

+0

vor kurzem ging ich durch ähnliches Problem - nur wenn ich absolute Pfad zu Bibliotheken in Class-Path-Problem schrieb verschwunden (~ didd nicht funktioniert) – xhudik

9

Da Scala Skripte die Scala Bibliotheken erfordern installiert werden, müssen Sie die Scala Laufzeit umfassen zusammen mit Ihrem KRUG.

Es gibt viele Strategien dafür, wie jar jar, aber das Problem, das Sie sehen, ist, dass der Java-Prozess, den Sie gestartet haben, die Scala JARs nicht finden kann.

Für ein einfaches Standalone-Skript würde ich empfehlen, jar jar zu verwenden, andernfalls sollten Sie sich ein Abhängigkeitsverwaltungstool ansehen oder von Benutzern die Installation von Scala im JDK verlangen.

+0

Ja, sollten Sie die Gläser Umpacken kein Problem aus einer Lizenz Sicht sein: http://www.scala-lang.org/node/146 – McDowell

4

Sie auch Maven und die Maven-scala-Plugin verwenden können. Sobald Sie Maven eingerichtet haben, können Sie einfach mvn-Paket machen und es wird Ihr Glas für Sie erstellen.

+1

Die generierte JAR enthält jedoch nicht scala-library.jar. –

+2

@simon - das ist die Aufgabe, Scala-Bibliothek in Abhängigkeiten in der Pom-Datei hinzuzufügen – xhudik

2

Ich modifizierte das Bash-Skript und fügte etwas Intelligenz hinzu, einschließlich automatischer Manifest-Generierung.

Dieses Skript geht davon aus, dass das Hauptobjekt genauso benannt ist wie die Datei, in der es sich befindet (Groß-/Kleinschreibung beachten).Außerdem muss entweder der aktuelle Verzeichnisname dem Hauptobjektnamen oder der Hauptobjektname als Befehlszeilenparameter angegeben werden. Starten Sie dieses Skript im Stammverzeichnis Ihres Projekts. Ändern Sie die Variablen an der Spitze wie erforderlich.

Beachten Sie, dass das Skript die Ordner bin und dist erzeugt 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 " " 
2

Eine Sache, die ein ähnliches Problem verursachen kann (obwohl es nicht das Problem oben in der ersten Frage) ist, dass der Java-VM zu fordern scheint, dass die wichtigste Methode void zurückzugibt. In Scala können wir so etwas wie (die = beobachten -sign in der Definition des Haupt) schreiben:

object MainProgram { 

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

wo Haupt eine tatsächlich gibt GUI -object (dh es ist nicht void), aber das Programm läuft gut wenn wir es mit dem Befehl scala starten.

Wenn wir diesen Code in eine Jar-Datei verpacken, mit MainProgram als Main-Klasse, wird die Java vm beschweren sich, dass es keine Hauptfunktion ist, da der Rückgabetyp unserer Haupt nicht void ist (ich finde diese Beschwerde etwas seltsam, da der Rückgabetyp nicht Teil der Signatur ist).

Wir hätten keine Probleme, wenn wir das = -Zeichen in der Kopfzeile von main weglassen oder wenn wir es explizit als Unit deklarierten.

5

Ich endete mit sbt assembly, es ist wirklich einfach zu bedienen. Ich habe eine Datei namens assembly.sbt in das Verzeichnis project/ an der Wurzel des Projekts mit einem einzigen Liner hinzugefügt (Beachten Sie, dass Ihre Version möglicherweise geändert werden muss).

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

Dann starten Sie einfach die assembly Aufgabe in sbt:

> assembly 

Or 'sbt Assembly' nur in der Projektstammverzeichnis

$ sbt assembly 

Es wird zuerst die Tests laufen und es wird dann erzeuge das neue jar im Verzeichnis target/ (vorausgesetzt, dass meine build.sbt bereits alle meine Abhängigkeiten auflistet).

In meinem Fall, mache ich nur, dass .jar Datei ausführbar, umbenennen, um die Erweiterung zu entfernen, und es ist bereit zu versenden!

Wenn Sie ein Befehlszeilen-Tool machen, vergessen Sie nicht, eine man page hinzuzufügen (Ich hasse Skripte ohne richtige Manpages oder mit mehrseitigen Klartext-Dokumentation, die nicht sogar in einen Pager für Sie piped ist).

3

Ich habe versucht, MyDowell Methode zu reproduzieren. Endlich könnte ich es zum Laufen bringen. Allerdings finde ich, dass die Antwort zwar für einen Neuling etwas zu kompliziert ist (insbesondere ist die Verzeichnisstruktur unnötig kompliziert).

Ich kann dieses Ergebnis mit sehr einfachen Mitteln reproduzieren. Zu Beginn 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 

erste Kompilierung helloworld.scala:

scalac helloworld.scala 

erstellen dann das Glas:

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

jetzt können Sie es laufen mit :

java -jar helloworld.jar 

fand ich diese einfache Lösung, weil das Original nicht funktionierte. Später habe ich das herausgefunden, nicht weil es falsch ist, sondern wegen eines trivialen Fehlers: Wenn ich die zweite Zeile in MANIFEST.MF nicht mit einer neuen Zeile schließe, wird diese Zeile ignoriert. Das hat eine Stunde gedauert, bis ich es herausgefunden habe und ich habe alle anderen Dinge vorher versucht und dabei diese sehr einfache Lösung gefunden.

3

Ich will nicht zu schreiben, warum und wie ist ziemlich einfach, die Lösung zeigen, was in meinem Fall war (über Linux Ubuntu Kommandozeile):

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 
0

Wenn Sie sbt Einrichtungen nicht nutzen wollen empfehle ich die Verwendung einer Make-Datei. Hier

ist ein Beispiel, wo foo Paket durch ersetzt foo.bar.myApp auf Vollständigkeit.

Make-Datei

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