Создание файла jar из файла Scala
Я новичок в Scala и не знаю Java. Я хочу создать файл jar из простого файла Scala. Итак, у меня есть HelloWorld.scala, создайте HelloWorld.jar.
Manifest.mf:
Main-Class: HelloWorld
В консоли я запускаю:
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)
Ответы
Ответ 1
Пример структуры каталога:
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
Чтобы успешно использовать ключ -jar, вам нужны две записи в файле META-INF/MANIFEST.MF: основной класс; относительные URL-адреса для любых зависимостей. Примечания к документации:
-jar
Выполнить программу, инкапсулированную в JAR файл. Первым аргументом является имя файла JAR вместо имя запуска. Для этого вариант работы, манифест Файл JAR должен содержать строку form Main-Class: classname. Вот, classname определяет класс, имеющий public static void main (String [] args), который служит вашим отправной точкой приложения. См. Справочная страница инструмента Jar и Jar след учебника Java для информация о работе с Jar файлов и манифеста Jar файла.
Когда вы используете эту опцию, файл JAR является источником всех пользовательских классов, и другие параметры пути пользовательского класса игнорируются.
(Примечания: JAR файлы могут быть проверены с большинством ZIP-приложений, я, вероятно, игнорирую обработку пробелов в именах каталогов в пакетной версии script; Scala runner версии 2.7.4.final.)
Для полноты эквивалент bash script:
#!/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
Ответ 2
Поскольку сценарии Scala требуют установки библиотек Scala, вам нужно будет включить runtime Scala вместе с вашим JAR.
Существует много стратегий для этого, например jar jar, но в конечном итоге проблема, которую вы видите, заключается в том, что процесс Java, вы не можете найти Scala JARs.
Для простой автономной script я бы рекомендовал использовать jar jar, иначе вы должны начать искать инструмент управления зависимостями или потребовать от пользователей установки Scala в JDK.
Ответ 3
Вы также можете использовать maven и maven- scala -plugin. После того, как вы установили maven, вы можете просто сделать mvn-пакет, и он создаст вашу банку для вас.
Ответ 4
В итоге я использовал сборку sbt, он действительно прост в использовании. Я добавил файл с именем assembly.sbt
в каталог project/
в корне проекта с одним лайнером (обратите внимание, что ваша версия, возможно, потребуется изменить).
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")
Затем просто запустите задачу assembly
в sbt
:
> assembly
Сначала будут выполняться ваши тесты, а затем он будет генерировать новую банку в каталог target/
(учитывая, что мой build.sbt
уже перечисляет все мои зависимости).
В моем случае я просто делаю исполняемый файл .jar
, переименовываю, чтобы удалить расширение, и он готов к отправке!
Кроме того, если вы выполняете инструмент командной строки, не забудьте добавить справочную страницу (я ненавижу скрипты без соответствующих manpages или с многостраничной текстовой документацией, которая даже не транслируется в пейджер для вас).
Ответ 5
Я не хочу писать, почему и как просто показать решение, которое работало в моем случае (через 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
Ответ 6
Я модифицировал bash script добавление некоторого интеллекта, включая генерацию авто-манифеста.
Этот script предполагает, что основной объект имеет то же имя, что и файл, в котором он находится (с учетом регистра). Кроме того, либо текущее имя каталога должно совпадать с именем основного объекта, либо имя основного объекта должно быть представлено как параметр командной строки. Запустите этот script из корневого каталога вашего проекта. При необходимости измените переменные вверху.
Имейте в виду, что script будет генерировать папки bin и dist и будет УДАЛИТЬ любое существующее содержимое в 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 " "
Ответ 7
Одна вещь, которая может вызвать аналогичную проблему (хотя это не проблема в начальном вопросе выше) заключается в том, что Java vm, кажется, требует, чтобы основной метод возвращал void
. В Scala мы можем написать что-то вроде (обратите внимание на = -sign в определении main):
object MainProgram {
def main(args: Array[String]) = {
new GUI(args)
}
}
где main фактически возвращает GUI
-объект (т.е. не void
), но программа будет работать хорошо, когда мы начнем ее с помощью команды Scala.
Если мы упакуем этот код в jar файл, а MainProgram
в качестве основного класса, Java vm будет жаловаться на отсутствие основной функции, так как тип возврата нашего основного не является void
(я нахожу эта жалоба несколько странная, так как тип возврата не является частью подписи).
У нас не было бы проблем, если бы мы оставили в заголовке main = -sign или явным образом объявили его как Unit
.
Ответ 8
Я попытался воспроизвести метод MyDowell. Наконец, я мог бы заставить его работать. Однако я считаю, что ответ хотя и слишком сложен для новичков (в частности, структура каталогов излишне сложна).
Я могу воспроизвести этот результат очень упрощенными способами. Для начала существует только один каталог, содержащий три файла:
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
сначала скомпилируйте helloworld.scala:
scalac helloworld.scala
затем создайте банку:
\progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF .
теперь вы можете запустить его с помощью:
java -jar helloworld.jar
Я нашел это простое решение, потому что оригинал не работал. Позже я узнал, что не потому, что это неправильно, а из-за тривиальной ошибки: если я не закрываю вторую строку в MANIFEST.MF с новой строкой, то эта строка будет проигнорирована. Это заняло у меня час, чтобы узнать, и я пробовал все остальное до этого, в процессе нахождения этого очень простого решения.
Ответ 9
Если вы не хотите использовать средства sbt, я рекомендую использовать make файл.
Вот пример, когда пакет foo заменяется на foo.bar.myApp для полноты.
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)" > [email protected]
@echo "Class-Path: scala-library.jar" >> [email protected]
$(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)[email protected]
.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