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)
Les scripts Scala nécessitant l’installation des bibliothèques Scala, vous devez donc inclure le runtime Scala avec votre fichier JAR.
Il existe plusieurs stratégies pour ce faire, telles que jar jar , mais le problème que vous constatez est que le processus Java que vous avez lancé ne parvient pas à 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 fini par utiliser sbt Assembly , c'est vraiment simple à utiliser. J'ai ajouté un fichier appelé Assembly.sbt
dans le répertoire project/
à 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 simplement la tâche Assembly
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 fichier .jar
exécutable, renommer pour supprimer l'extension et il est prêt à être expédié!
De plus, si vous utilisez un outil en ligne de commande, n'oubliez pas d'ajouter une page de manuel (je déteste les scripts sans pages de manuel appropriées ou avec une documentation de plusieurs pages en texte brut qui n'est même pas transférée dans un 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
première compilation 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
Une chose qui peut poser 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 main renvoie void
. En Scala, nous pouvons écrire quelque chose comme ( observe 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 void
), mais le programme fonctionnera correctement lorsque nous le lancerons à l'aide de la commande scala.
Si nous empaquetons ce code dans un fichier jar, avec MainProgram
comme classe principale, Java vm se plaindra de l'absence de fonction principale, car le type de retour de notre classe principale n'est pas void
(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 = -sign dans l'en-tête de main ou si nous le déclarions explicitement comme étant Unit
.
J'ai modifié le script bash en y ajoutant une certaine intelligence, y compris la génération de manifeste automatique.
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 " "
Si vous ne souhaitez pas utiliser les installations sbt, je vous recommande d'utiliser un fichier makefile.
Voici un exemple où foo package est remplacé par foo.bar.myApp pour des informations complètes.
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