Criando um arquivo jar de um arquivo de Scala
Pergunta
Eu sou novo para Scala e não sei Java. Eu quero criar um arquivo jar fora de um arquivo Scala simples. Então, eu tenho o meu HelloWorld.scala, gerar um HelloWorld.jar.
Manifest.mf:
Main-Class: HelloWorld
No console eu 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)
Solução
estrutura de diretórios de amostra:
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 poder usar com sucesso o jar switch, você precisa de duas entradas no META-INF / MANIFEST.MF arquivo: a classe principal; URLs relativos a todas as dependências. As notas de documentação:
jar
Executar um programa encapsulado em um arquivo JAR. O primeiro argumento é o nome de um arquivo JAR em vez de um nome da classe de inicialização. Para que isso opção de trabalho, o manifesto do arquivo JAR deve conter uma linha do formar Main-Classe: classname. Aqui, identifica a classe classname possuindo o public static void main (String [] args) método que serve como seu aplicação é o ponto de partida. Veja o página de referência ferramenta do frasco e do frasco Trail of the Tutorial Java para informações sobre como trabalhar com Jar arquivos e manifesta-arquivo JAR.
Quando você usa esta opção, o arquivo JAR é a fonte de todas as classes de usuário, configurações de caminho de classe de usuário e outros são ignorados.
(Notas:. Arquivos JAR pode ser inspecionado com a maioria ZIP aplicações; eu provavelmente negligência lidar com espaços em nomes de diretório no script lote; Scala versão corredor código 2.7.4.final)
Para completar, um script bash equivalentes:
#!/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
Outras dicas
Como os scripts Scala requerem as bibliotecas Scala a serem instalados, você terá que incluir o tempo de execução Scala juntamente com o seu JAR.
Há muitas estratégias para fazer isso, como Jar Jar , mas em última análise, a questão você está vendo é que o processo de Java você começou a não conseguir encontrar o Scala JARs.
Para um script autônomo simples, eu recomendo usar Jar Jar, caso contrário, você deve começar a olhar para uma ferramenta de gerenciamento de dependência, ou exigir que os usuários instalem Scala no JDK .
Acabei usando sbt montagem , é realmente simples de usar. Eu adicionei um arquivo chamado assembly.sbt
no diretório project/
na raiz do projeto, com um um forro (Nota a sua versão pode precisar ser alterado).
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
Em seguida, basta executar a tarefa assembly
em sbt
:
> assembly
Ou apenas 'sbt assembly' no diretório raiz do projeto
$ sbt assembly
Ele vai primeiro executar os testes e, em seguida, ele irá gerar o novo jar para o diretório target/
(dado que a minha build.sbt
já lista todas as minhas dependências).
No meu caso, eu só fazer essa arquivo executável .jar
, mudar o nome para remover a extensão e está pronto para enviar!
Além disso, se você está fazendo uma ferramenta de linha de comando, não se esqueça de adicionar um homem página (eu odeio scripts sem manpages adequadas ou com várias páginas de documentação de texto simples que nem sequer é canalizada para um pager para você).
Você também pode utilizar o Maven e o maven-scala-plugin. Depois de configurar o Maven, você pode apenas fazer pacote mvn e ele irá criar o seu jar para você.
Eu tentei reproduzir o método de MyDowell. Finalmente, eu poderia fazê-lo funcionar. No entanto eu acho que a resposta que corrigir um pouco complicado para um novato (em particular a estrutura do diretório é desnecessariamente complicado).
Eu posso reproduzir este resultado com meios muito simplistas. Para começar com há apenas um diretório que contém três arquivos:
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
primeira HelloWorld.scala compilação:
scalac helloworld.scala
, em seguida, criar o jar:
\progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF .
Agora você pode executá-lo com:
java -jar helloworld.jar
Eu encontrei esta solução simples, porque o original não funcionou. Mais tarde eu descobri que não porque é errado, mas devido a um erro trivial: se eu não fecham a segunda linha no MANIFEST.MF com uma nova linha, a seguir esta linha será ignorado. Isso levou-me uma hora para descobrir e eu tentei todas as outras coisas antes, no processo de encontrar essa solução muito simples.
Eu não quero escrever porquê e como é bastante apenas mostrar a solução que funcionou no meu caso (via Linux Ubuntu linha de comando):
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
Eu modifiquei o script bash adicionando alguma inteligência incluindo geração de auto-manifesto.
Este script assume que o objeto principal é o mesmo nome que o arquivo está em (case sensitive). Além disso, o nome do diretório atual deve ser igual ao nome do objeto principal ou o nome do objeto principal deve ser fornecida como um parâmetro de linha de comando. Lançar este script a partir do diretório raiz do seu projeto. Modificar as variáveis ??no topo, conforme necessário.
Esteja ciente de que o script irá gerar o bin e pastas dist e apagar qualquer conteúdo existente no 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 " "
Uma coisa que pode causar um problema semelhante (embora não é o problema em questão inicial acima) é que o Java VM parece exigir que o principal método retorna void
. Em Scala podemos escrever algo como ( observar a = -sign na definição do principal ):
object MainProgram {
def main(args: Array[String]) = {
new GUI(args)
}
}
onde principal realmente retorna um GUI
-objeto (ou seja, não é void
), mas o programa será executado muito bem quando iniciá-lo usando o comando scala.
Se empacotar esse código em um arquivo jar, com MainProgram
como o Main-Class, o Java vm se queixam de que não há nenhuma função principal, uma vez que o tipo de retorno do nosso principal não é void
(acho que esta queixa um pouco estranho , uma vez que o tipo de retorno não é parte da assinatura).
Nós não teria problemas se deixou de fora a = -sign no cabeçalho do principal, ou se declarou explicitamente como Unit
.
Se você não quiser usar instalações sbt eu recomendo o uso de um makefile.
Aqui está um exemplo onde foo pacote é substituído por foo.bar.myApp para ser completo.
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