Domanda

Sono nuovo di Scala e non conosco Java. Voglio creare un file jar da un semplice file Scala. Quindi ho il mio HelloWorld.scala, genera un HelloWorld.jar.

manifest.mf:

Main-Class: HelloWorld

Nella console corro:

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)
È stato utile?

Soluzione

Struttura della directory di esempio:

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

Per utilizzare correttamente l'opzione -jar , sono necessarie due voci nel file META-INF / MANIFEST.MF : la classe principale; URL relativi a eventuali dipendenze. Le note di documentazione:

  

-jar

     

Esegue un programma incapsulato in a   File JAR. Il primo argomento è il   nome di un file JAR invece di a   nome della classe di avvio. Per questo   opzione di lavoro, il manifest del   Il file JAR deve contenere una riga del file   form Main-Class: classname. Qui,   classname identifica la classe che ha   il vuoto statico pubblico principale (String []   args) che funge da tuo   punto di partenza dell'applicazione. Vedi il   Pagina di riferimento dello strumento Jar e Jar   traccia del tutorial Java per   informazioni su come lavorare con Jar   file e manifest di file Jar.

     

Quando si utilizza questa opzione, il file JAR   è la fonte di tutte le classi di utenti,    e le altre impostazioni del percorso della classe utente vengono ignorate.

(Note: i file JAR possono essere controllati con la maggior parte delle applicazioni ZIP; probabilmente trascuro la gestione degli spazi nei nomi delle directory nello script batch; Runner code code versione 2.7.4.final.)


Per completezza, uno 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

Altri suggerimenti

Poiché gli script Scala richiedono l'installazione delle librerie Scala, dovrai includere il runtime Scala insieme al tuo JAR.

Esistono molte strategie per farlo, come jar jar , ma alla fine il problema stai vedendo è che il processo Java che hai avviato non riesce a trovare i JAR Scala.

Per un semplice script autonomo, consiglierei di usare jar jar, altrimenti dovresti iniziare a cercare uno strumento di gestione delle dipendenze o richiedere agli utenti di installare Scala in JDK .

Ho finito con sbt assembly , è davvero semplice da usare. Ho aggiunto un file chiamato assembly.sbt nella directory project/ alla radice del progetto con un solo liner (nota che potrebbe essere necessario modificare la versione).

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

Quindi esegui l'attività assembly in sbt:

> assembly

O semplicemente 'sbt assembly' nella directory root del progetto

$ sbt assembly

Prima eseguirà i test e quindi genererà il nuovo jar nella directory target/ (dato che il mio build.sbt elenca già tutte le mie dipendenze).

Nel mio caso, rendo solo quel file eseguibile .jar, rinomino per rimuovere l'estensione ed è pronto per la spedizione!

Inoltre, se stai utilizzando uno strumento da riga di comando, non dimenticare di aggiungere un pagina man (odio gli script senza le pagine man appropriate o con la documentazione di testo semplice multipagina che non è nemmeno inserita in un cercapersone per te).

Puoi anche usare maven e il plugin maven-scala. Una volta installato maven, puoi semplicemente fare il pacchetto mvn e questo creerà il tuo vaso per te.

Ho provato a riprodurre il metodo di MyDowell. Finalmente sono riuscito a farlo funzionare. Tuttavia trovo che la risposta sia corretta, sebbene un po 'troppo complicata per un principiante (in particolare la struttura della directory è inutilmente complicata).

Posso riprodurre questo risultato con mezzi molto semplicistici. Per cominciare c'è solo una directory che contiene tre file:

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

prima compilazione helloworld.scala:

scalac helloworld.scala

quindi crea il vaso:

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

ora puoi eseguirlo con:

java -jar helloworld.jar

Ho trovato questa soluzione semplice perché quella originale non funzionava. Più tardi ho scoperto che non perché è sbagliato, ma a causa di un banale errore: se non chiudo la seconda riga in MANIFEST.MF con una nuova riga, questa riga verrà ignorata. Mi ci è voluta un'ora per scoprirlo e prima ho provato tutte le altre cose, trovando questa soluzione molto semplice.

Non voglio scrivere perché e come è piuttosto mostrare la soluzione che ha funzionato nel mio caso (tramite la riga di comando di Ubuntu Linux):

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

Ho modificato lo script bash aggiungendo un po 'di intelligenza tra cui la generazione auto-manifest.

Questo script presuppone che l'oggetto principale abbia lo stesso nome del file in cui si trova (case sensitive). Inoltre, il nome della directory corrente deve essere uguale al nome dell'oggetto principale oppure il nome dell'oggetto principale deve essere fornito come parametro della riga di comando. Avvia questo script dalla directory principale del tuo progetto. Modifica le variabili in alto come richiesto.

Ricorda che lo script genererà le cartelle bin e dist e ELIMINERÀ tutti i contenuti esistenti nel 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 che può causare un problema simile (anche se non è il problema nella domanda iniziale sopra) è che Java vm sembra richiedere che il metodo principale restituisca void. In Scala possiamo scrivere qualcosa del tipo ( osserva il segno = nella definizione di main ):

object MainProgram {

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

dove main restituisce effettivamente un oggetto GUI (ovvero non è MainProgram), ma il programma funzionerà bene quando lo avvieremo usando il comando scala.

Se impacchettiamo questo codice in un file jar, con Unit come Main-Class, Java vm si lamenterà che non esiste alcuna funzione principale, poiché il tipo di ritorno del nostro main non è <=> (trovo questa lamentela è alquanto strana, poiché il tipo restituito non fa parte della firma).

Non avremmo problemi se tralasciassimo il segno = nell'intestazione di main o se lo dichiarassimo esplicitamente come <=>.

Se non si desidera utilizzare le strutture sbt, si consiglia l'uso di un makefile.

Ecco un esempio in cui il pacchetto foo è sostituito da foo.bar.myApp per completezza.

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top