Création d'un fichier JAR à partir d'un fichier Scala
Question
Je suis nouveau sur Scala et je ne connais pas Java. Je veux créer un fichier jar à partir d'un simple fichier Scala. J'ai donc mon HelloWorld.scala, générer un HelloWorld.jar.
Manifest.mf:
Main-Class: HelloWorld
Dans la console, je lance:
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)
La solution
Exemple de structure de répertoire:
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
Pour utiliser avec succès le commutateur -jar , vous devez disposer de deux entrées dans le fichier META-INF / MANIFEST.MF : la classe principale; URL relatives à toutes les dépendances. Les notes de documentation:
-jar
Exécuter un programme encapsulé dans un Fichier JAR. Le premier argument est le nom d'un fichier JAR au lieu d'un nom de la classe de démarrage. Pour cela option de travail, le manifeste de la Le fichier JAR doit contenir une ligne du formulaire Main-Class: nom de classe. Ici, nom de classe identifie la classe ayant public static void main (String [] args) qui sert de votre point de départ de l'application. Voir le Page de référence de l'outil Jar et le Jar piste du tutoriel Java pour informations sur travailler avec Jar des fichiers et des manifestes de fichiers Jar.
Lorsque vous utilisez cette option, le fichier JAR est la source de toutes les classes d'utilisateurs, et les autres paramètres de chemin d'accès aux classes d'utilisateurs sont ignorés.
(Remarques: les fichiers JAR peuvent être inspectés avec la plupart des applications ZIP; je néglige probablement de gérer les espaces dans les noms de répertoire dans le script batch; Scala code runner version 2.7.4.final.)
Pour être complet, un script bash équivalent:
#!/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
Autres conseils
Les scripts Scala nécessitant l’installation des bibliothèques Scala, vous devez donc inclure le runtime Scala avec votre fichier JAR.
Il existe de nombreuses stratégies pour ce faire, telles que jar jar , mais finalement le problème vous constatez que le processus Java que vous avez lancé ne permet pas de trouver les fichiers JAR Scala.
Pour un script autonome simple, je vous recommande d'utiliser jar jar, sinon vous devriez commencer à rechercher un outil de gestion des dépendances ou demander aux utilisateurs d'installer Scala dans le JDK .
J'ai utilisé sbt assembly , son utilisation est vraiment simple. J'ai ajouté un fichier appelé assembly.sbt
dans le répertoire project/
situé à la racine du projet avec une ligne (notez que votre version devra peut-être être modifiée).
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
Ensuite, exécutez la assembly
tâche dans sbt
:
> assembly
Ou simplement 'sbt assembly' dans le répertoire racine du projet
$ sbt assembly
Il exécutera d'abord vos tests, puis générera le nouveau fichier jar dans le répertoire target/
(étant donné que mon build.sbt
répertorie déjà toutes mes dépendances).
Dans mon cas, je viens de rendre ce .jar
fichier exécutable, le renommer pour supprimer l'extension et il est prêt à être expédié!
De même, si vous utilisez un outil de ligne de commande, n'oubliez pas d'ajouter un page de manuel (je déteste les scripts sans pages de manuel appropriées ou avec une documentation en texte brut de plusieurs pages qui ne figure même pas dans un pager pour vous).
Vous pouvez également utiliser maven et le plugin maven-scala-plugin. Une fois que vous avez configuré Maven, vous pouvez créer un package MVN qui créera votre jarre pour vous.
J'ai essayé de reproduire la méthode de MyDowell. Enfin, je pourrais le faire fonctionner. Cependant, je trouve que la réponse, bien que correcte, est un peu trop compliquée pour un novice (en particulier, la structure des répertoires est inutilement compliquée).
Je peux reproduire ce résultat avec des moyens très simplistes. Pour commencer, il n’existe qu’un seul répertoire contenant trois fichiers:
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
compilez d'abord helloworld.scala:
scalac helloworld.scala
puis créez le pot:
\progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF .
maintenant vous pouvez le lancer avec:
java -jar helloworld.jar
J'ai trouvé cette solution simple car l'original ne fonctionnait pas. Plus tard, j'ai découvert que ce n'était pas parce que c'était faux, mais à cause d'une erreur triviale: si je ne ferme pas la deuxième ligne dans MANIFEST.MF avec une nouvelle ligne, cette ligne sera ignorée. Cela m'a pris une heure pour le découvrir et j'ai déjà essayé toutes les autres choses avant de trouver cette solution très simple.
Je ne veux pas écrire pourquoi et comment montrer simplement la solution qui a fonctionné dans mon cas (via la ligne de commande 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
J'ai modifié le script bash en ajoutant une intelligence, notamment la génération de manifestes automatiques.
Ce script suppose que l'objet principal porte le même nom que le fichier dans lequel il se trouve (sensible à la casse). De plus, le nom du répertoire actuel doit être égal au nom de l'objet principal ou doit être fourni en tant que paramètre de ligne de commande. Lancez ce script à partir du répertoire racine de votre projet. Modifiez les variables en haut si nécessaire.
Sachez que le script générera les dossiers bin et dist et effacera tout contenu existant dans 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 " "
Une chose qui peut causer un problème similaire (bien que ce ne soit pas le problème de la question initiale ci-dessus) est que Java vm semble exiger que la méthode principale retourne void
. En Scala, nous pouvons écrire quelque chose comme ( observez le signe = dans la définition de main ):
object MainProgram {
def main(args: Array[String]) = {
new GUI(args)
}
}
où main renvoie en fait un objet GUI
(c'est-à-dire que ce n'est pas MainProgram
), mais le programme s'exécute correctement lorsque nous le démarrons à l'aide de la commande scala.
Si nous empaquetons ce code dans un fichier jar, avec Unit
en tant que classe principale, Java vm se plaint de l'absence de fonction principale, car le type de retour de notre classe principale n'est pas <=> (je trouve cette plainte quelque peu étrange, car le type de retour ne fait pas partie de la signature).
Nous n'aurions aucun problème si nous omettions le signe = dans l'en-tête de main ou si nous le déclarions explicitement comme <=>.
Si vous ne souhaitez pas utiliser les installations sbt, je vous recommande d'utiliser un fichier makefile.
Voici un exemple où le package toto est remplacé par toto.bar.myApp pour plus de détails.
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