Вопрос

Я новичок в Scala и не знаю Java.Я хочу создать файл jar из простого файла Scala.Итак, у меня есть HelloWorld.scala, я создаю HelloWorld.jar.

Манифест.мф:

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)
Это было полезно?

Решение

Пример структуры каталогов:

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!")
    }
  }
}

МАНИФЕСТ.МФ:

Main-Class: foo.HelloWorld
Class-Path: scala-library.jar

сборка.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

Чтобы успешно использовать -банка переключатель, вам нужны две записи в МЕТА-ИНФ/МАНИФЕСТ.МФ файл:основной класс;относительные URL-адреса для любых зависимостей.В документации отмечается:

-банка

Выполните программу, инкапсулированную в файл JAR.Первый аргумент - это имя файла JAR вместо имени класса запуска.Чтобы эта опция работала, манифест файла JAR должен содержать строку из основного класса формы:имя класса.Здесь ClassName идентифицирует класс, обладающий публичным статическим void main (String [] args) методом, который служит отправной точкой вашего приложения.См. Справочную страницу инструмента JAR и The Jar Trail of the Java Tutorial для получения информации о работе с файлами JAR и манифестами JAR-FILE.

Когда вы используете эту опцию, файл JAR является источником всех классов пользователей, и другие настройки пути к классу пользователя игнорируются.

(Примечания:Файлы JAR можно проверять с помощью большинства ZIP-приложений;Я, вероятно, пренебрегаю обработкой пробелов в именах каталогов в пакетном скрипте;Средство запуска кода Scala версии 2.7.4.final.)


Для полноты эквивалентный сценарий bash:

#!/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

Другие советы

Поскольку сценарии Scala требуют установки библиотек Scala, вам придется включить среду выполнения Scala вместе с JAR-файлом.

Для этого существует множество стратегий, например банка, но в конечном итоге проблема, с которой вы сталкиваетесь, заключается в том, что запущенный вами процесс Java не может найти файлы JAR Scala.

Для простого автономного сценария я бы рекомендовал использовать jar jar, в противном случае вам следует начать искать инструмент управления зависимостями или потребовать от пользователей установить Scala. в JDK.

в итоге я использовал сборка сбт, он действительно прост в использовании.Я добавил файл под названием assembly.sbt в project/ каталог в корне проекта с одной строкой (обратите внимание, что вашу версию, возможно, придется изменить).

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2")

Затем просто запустите assembly задача в sbt:

> assembly

Или просто «сборка sbt» в корневом каталоге проекта.

$ sbt assembly

Сначала он запустит ваши тесты, а затем сгенерирует новый jar-файл в target/ каталог (учитывая, что мой build.sbt уже перечислены все мои зависимости).

В моем случае я просто делаю это .jar исполняемый файл, переименуйте, чтобы удалить расширение, и он готов к отправке!

Кроме того, если вы используете инструмент командной строки, не забудьте добавить справочная страница (Я ненавижу сценарии без соответствующих страниц руководства или с многостраничной текстовой документацией, которая даже не передается вам в пейджер).

Вы также можете использовать maven и плагин maven-scala-plugin.После того, как вы настроите maven, вы можете просто создать пакет mvn, и он создаст для вас jar.

Я попытался воспроизвести метод MyDowell.Наконец-то я смог заставить это работать.Однако я считаю, что правильный ответ слишком сложен для новичка (в частности, структура каталогов излишне сложна).

Я могу воспроизвести этот результат очень упрощенными средствами.Начнем с того, что есть только один каталог, содержащий три файла:

helloworld.scala
MANIFEST.MF
scala-library.jar

helloworld.scala

object HelloWorld
{
  def main(args: Array[String])
  {
    println("Hello, world!")
  }
}

МАНИФЕСТ.МФ:

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 новой строкой, то эта строка будет проигнорирована.Мне потребовался час, чтобы выяснить это, и я пробовал все остальное раньше, пока нашел это очень простое решение.

Я не хочу писать, почему и как, а просто покажу решение, которое сработало в моем случае (через командную строку 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

Я модифицировал bash-скрипт, добавив немного интеллекта, включая автоматическое создание манифеста.

Этот сценарий предполагает, что основной объект имеет то же имя, что и файл, в котором он находится (с учетом регистра).Кроме того, либо имя текущего каталога должно совпадать с именем основного объекта, либо имя основного объекта должно быть указано в качестве параметра командной строки.Запустите этот скрипт из корневого каталога вашего проекта.Измените переменные вверху по мере необходимости.

Имейте в виду, что сценарий создаст папки 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 " "

Одна вещь, которая может вызвать подобную проблему (хотя это не проблема в исходном вопросе выше), заключается в том, что виртуальная машина Java, похоже, требует, чтобы основной метод возвращал void.В Scala мы можем написать что-то вроде (обратите внимание на знак = в определении основного):

object MainProgram {

  def main(args: Array[String]) = {
    new GUI(args)
  }
}

где main фактически возвращает GUI-объект (т.это не void), но программа будет работать нормально, если мы запустим ее с помощью команды scala.

Если мы упакуем этот код в jar-файл с MainProgram как основной класс, виртуальная машина Java будет жаловаться на отсутствие основной функции, поскольку тип возвращаемого значения нашей основной функции не равен void (Я нахожу эту жалобу несколько странной, поскольку тип возвращаемого значения не является частью подписи).

У нас не было бы проблем, если бы мы опустили знак = в заголовке main или явно объявили его как Unit.

Если вы не хотите использовать возможности sbt, я рекомендую использовать make-файл.

Вот пример, где фу пакет заменяется на foo.bar.myApp для полноты.

make-файл

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top