2009-04-30 29 views
46

Soy nuevo en Scala y no conozco Java. Quiero crear un archivo jar a partir de un simple archivo Scala. Así que tengo mi HelloWorld.scala, genero un HelloWorld.jar.Crear un archivo jar desde un archivo de Scala

MANIFEST.MF:

Main-Class: HelloWorld 

En la consola que ejecute:

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

¿Se acordó incluir un manifiesto en el .jar? – millimoose

+0

¿Qué clase dice que no se encuentra? –

+0

Además, ¿compiló Scala en bytecode primero? – millimoose

Respuesta

53

Muestra estructura de directorios:

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 

Con el fin de utilizar correctamente el interruptor -jar, se necesitan dos entradas en el archivo META-INF /MANIFEST.MF: la clase principal; URL relativas a cualquier dependencia. Las notas de la documentación:

-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.

(Notas: archivos JAR pueden ser inspeccionados con la mayoría de las aplicaciones ZIP; probablemente el abandono manejo de espacios en los nombres de directorio en el archivo por lotes ; Corredor de código Scala versión 2.7.4.final.)


Para completar, una escritura del golpe equivalente:

#!/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

Seguí estas instrucciones a la carta (copiando y pegando y configurando el directorios son sugieres) y me sale el error: java-jar hellow.jar Excepción en el hilo "principal" java.lang.NoClassDefFoundError: Scala/ScalaObject \t en java.lang.ClassLoader.defineClass1 (nativo Método) \t en java.lang.ClassLoader.defineClass (ClassLoader.java: 676) \t en java.security.SecureClassLoader.defineClass (SecureClassLoader.java:124) \t en java.net.URLClassLoader.defineClass (URLClassLoader.java:260) . Estructura de árbol: http://pastebin.com/3k8rCpxf – I82Much

+0

@ I82Much - No veo ningún problema obvio con la estructura de tu árbol; Deshago hellow.jar y verifico que el archivo de manifiesto tiene la dependencia correcta. – McDowell

+0

Recientemente tuve un problema similar - solo cuando escribí la ruta absoluta a las bibliotecas en Class-Path desapareció el problema (~ diddn't work) – xhudik

9

Debido a que las secuencias de comandos Scala requieren las bibliotecas Scala para ser instalados, tendrá que incluir el tiempo de ejecución Scala junto con su TARRO.

Existen muchas estrategias para hacerlo, como jar jar, pero al final el problema que está viendo es que el proceso de Java que ha iniciado no puede encontrar los archivos JAR de Scala.

Para una secuencia de comandos simple, recomiendo usar jar jar; de lo contrario, debería comenzar a buscar una herramienta de administración de dependencias o solicitar a los usuarios que instalen Scala en el JDK.

+0

Sí, reempaquetar los frascos no debería ser un problema desde el punto de vista de la licencia: http://www.scala-lang.org/node/146 – McDowell

4

También puede utilizar Maven y el maven-scala-plugin. Una vez que configura maven, puede hacer el paquete mvn y creará su jar por usted.

+1

Sin embargo, el JAR generado no incluye scala-library.jar. –

+2

@simon - esto depende de usted para agregar scala-library en dependencias en el archivo pom – xhudik

2

Modifiqué el script bash agregando algo de inteligencia, incluida la generación de manifiesto automático.

Este script asume que el objeto principal se llama igual que el archivo en el que se encuentra (distingue entre mayúsculas y minúsculas).Además, el nombre del directorio actual debe ser igual al nombre del objeto principal o el nombre del objeto principal debe proporcionarse como un parámetro de línea de comando. Inicie este script desde el directorio raíz de su proyecto. Modifique las variables en la parte superior según sea necesario.

Tenga en cuenta que la secuencia de comandos generará las carpetas bin y dist y BORRARÁ cualquier contenido existente en la bandeja.


#!/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

Una cosa que puede causar un problema similar (aunque no es el problema en la pregunta inicial arriba) es que la máquina virtual de Java parece exigir que el principal método devuelve void. En Scala podemos escribir algo como (observar los -sign = en la definición de principal):

object MainProgram { 

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

donde principal en realidad devuelve un -objeto GUI (es decir, que no es void), pero el programa funcionará muy bien cuando lo comenzamos usando el comando scala.

Si empaquetamos este código en un archivo jar, con MainProgram como clase principal, Java vm se quejará de que no hay función principal, ya que el tipo de devolución de nuestro main no es void (esta queja me parece un tanto extraño, ya que el tipo de devolución no es parte de la firma).

No tendríamos problemas si omitimos el = -sign en el encabezado de main, o si lo declaramos explícitamente como Unit.

5

Terminé utilizando sbt assembly, es realmente fácil de usar. Agregué un archivo llamado assembly.sbt en el directorio project/ en la raíz del proyecto con un trazador de líneas (tenga en cuenta que su versión podría necesitar ser cambiada).

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

A continuación, basta con ejecutar la tarea assembly en sbt:

> assembly 

O simplemente 'asamblea SBT' en el directorio raíz del proyecto

$ sbt assembly 

En primer lugar, se ejecutará sus pruebas y luego se generar el nuevo jar en el directorio target/ (dado que mi build.sbt ya enumera todas mis dependencias).

En mi caso, simplemente hago que el archivo .jar sea ejecutable, cambie el nombre para eliminar la extensión y ¡está listo para enviar!

Además, si está haciendo una herramienta de línea de comandos, no olvide agregar man page (odio las secuencias de comandos sin páginas de manual o con la documentación de texto sin formato de varias páginas que ni siquiera está conectada a un buscapersonas).

3

Intenté reproducir el método de MyDowell. Finalmente podría hacerlo funcionar. Sin embargo, creo que la respuesta correcta es demasiado complicada para un principiante (en particular, la estructura del directorio es innecesariamente complicada).

Puedo reproducir este resultado con medios muy simples. Para empezar, solo hay un directorio que contiene tres archivos:

helloworld.scala 
MANIFEST.MF 
scala-library.jar 

helloworld.Scala

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

MANIFIEST.MF:

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

primera helloworld.scala de compilación:

scalac helloworld.scala 

continuación, crear el frasco:

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

Ahora se puede ejecutar con :

java -jar helloworld.jar 

Encontré esta solución simple porque la original no funcionó. Más tarde descubrí que no porque sea incorrecto, sino por un error trivial: si no cierro la segunda línea en MANIFEST.MF con una nueva línea, esta línea se ignorará. Esto me llevó una hora averiguarlo y probé todas las demás cosas antes, en el proceso de encontrar esta solución muy simple.

3

no quiero escribir por qué y cómo está más bien sólo mostrar la solución que funcionó en mi caso (a través de la línea de comandos de 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 
0

Si no desea utilizar las instalaciones del SBT que recomiendan el uso de un makefile.

Aquí se muestra un ejemplo donde foo paquete se sustituye por foo.bar.myApp para la integridad.

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 
Cuestiones relacionadas