Pregunta

Soy nuevo en Scala y no conozco Java. Quiero crear un archivo jar a partir de un archivo Scala simple. Entonces tengo mi HelloWorld.scala, generar un HelloWorld.jar.

Manifiesto.mf:

Main-Class: HelloWorld

En la consola ejecuto:

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)
¿Fue útil?

Solución

Estructura de directorio de muestra:

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

Para utilizar con éxito el modificador -jar , necesita dos entradas en el archivo META-INF / MANIFEST.MF : la clase principal; URL relativas a cualquier dependencia. Las notas de documentación:

  

-jar

     

Ejecuta un programa encapsulado en un   Archivo JAR. El primer argumento es el   nombre de un archivo JAR en lugar de un   Nombre de la clase de inicio. Para esto   opción de trabajar, el manifiesto de la   El archivo JAR debe contener una línea de   form Main-Class: nombre de clase. Aquí,   nombre de clase identifica la clase que tiene   el público estático vacío principal (Cadena []   método) que sirve como su   punto de partida de la aplicación. Ver el   Página de referencia de la herramienta Jar y Jar   rastro del tutorial de Java para   información sobre trabajar con Jar   archivos y manifiestos de archivo Jar.

     

Cuando usa esta opción, el archivo JAR   es la fuente de todas las clases de usuarios,    y otras configuraciones de ruta de clase de usuario se ignoran.

(Notas: los archivos JAR se pueden inspeccionar con la mayoría de las aplicaciones ZIP; probablemente descuido el manejo de espacios en los nombres de directorio en el script por lotes; Scala code runner versión 2.7.4.final.)


Para completar, un script bash 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

Otros consejos

Debido a que los scripts de Scala requieren que se instalen las bibliotecas de Scala, deberá incluir el tiempo de ejecución de Scala junto con su JAR.

Hay muchas estrategias para hacerlo, como jar jar , pero en última instancia el problema lo que está viendo es que el proceso Java que ha comenzado no puede encontrar los JAR de Scala.

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

Terminé usando 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 una sola línea (tenga en cuenta que es posible que deba cambiar su versión).

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

Luego simplemente ejecute la tarea assembly en sbt:

> assembly

O simplemente 'sbt assembly' en el directorio raíz del proyecto

$ sbt assembly

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

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

Además, si está utilizando una herramienta de línea de comandos, no olvide agregar un página de manual (odio las secuencias de comandos sin páginas de manual adecuadas o con documentación de texto plano de varias páginas que ni siquiera está en un buscapersonas para usted).

También puede usar maven y el complemento maven-scala. Una vez que configure maven, puede hacer mvn package y creará su jar para usted.

Traté de reproducir el método MyDowell. Finalmente pude hacerlo funcionar. Sin embargo, encuentro que la respuesta, aunque correcta, es demasiado complicada para un novato (en particular, la estructura del directorio es innecesariamente complicada).

Puedo reproducir este resultado con medios muy simplistas. 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!")
  }
}

MANIFEST.MF:

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

primera compilación helloworld.scala:

scalac helloworld.scala

luego crea el jar:

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

ahora puedes ejecutarlo con:

java -jar helloworld.jar

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

No quiero escribir por qué y cómo, sino 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

Modifiqué el script bash agregando algo de inteligencia, incluida la generación de auto-manifiesto.

Este script supone 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 el script generará las carpetas bin y dist y BORRARÁ cualquier contenido existente en 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 " "

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

object MainProgram {

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

donde main realmente devuelve un objeto GUI (es decir, no es MainProgram), pero el programa se ejecutará bien cuando lo iniciemos usando el comando scala.

Si empaquetamos este código en un archivo jar, con Unit como Main-Class, Java vm se quejará de que no hay una función principal, ya que el tipo de retorno de nuestro main no es <=> (encuentro esta queja es algo extraña, ya que el tipo de retorno no es parte de la firma).

No tendríamos problemas si omitiéramos el = -sign en el encabezado de main, o si lo declaramos explícitamente como <=>.

Si no desea utilizar las instalaciones de sbt, le recomiendo el uso de un archivo make.

Aquí hay un ejemplo donde el paquete foo se reemplaza por foo.bar.myApp para completarlo.

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top