Создание файла 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