2009-04-30 45 views
46

Tôi mới sử dụng Scala và không biết Java. Tôi muốn tạo một tệp jar từ một tệp Scala đơn giản. Vì vậy, tôi có HelloWorld.scala, tạo HelloWorld.jar.Tạo tệp jar từ tệp Scala

MANIFEST.MF:

Main-Class: HelloWorld 

Trong giao diện điều khiển tôi chạy:

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

Bạn nhớ để bao gồm một biểu hiện trong .jar? – millimoose

+0

Lớp học nói gì không được tìm thấy? –

+0

Ngoài ra, bạn đã biên dịch Scala thành bytecode trước chưa? – millimoose

Trả lời

53

Mẫu cấu trúc thư mục:

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 

Để sử dụng thành công trong việc chuyển đổi -jar, bạn cần hai mục trong META-INF/MANIFEST.MF file: lớp chính; URL tương đối với bất kỳ phụ thuộc nào. Các thuyết minh tài liệu:

-jar

Execute a program encapsulated in a JAR file. The first argument is the name of a JAR file instead of a startup class name. In order for this option to work, the manifest of the JAR file must contain a line of the form Main-Class: classname. Here, classname identifies the class having the public static void main(String[] args) method that serves as your application's starting point. See the Jar tool reference page and the Jar trail of the Java Tutorial for information about working with Jar files and Jar-file manifests.

When you use this option, the JAR file is the source of all user classes, and other user class path settings are ignored.

(Ghi chú: các tập tin JAR có thể được kiểm tra với hầu hết các ứng dụng ZIP; tôi có lẽ bỏ bê xử lý khoảng trống trong tên thư mục trong kịch bản hàng loạt ; Mã phiên bản Á hậu Scala 2.7.4.final.)


Để hoàn chỉnh, một bash script tương đương:

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

Tôi đã làm theo các hướng dẫn này cho chữ cái (sao chép và dán và thiết lập danh bạ là bạn đề nghị) và tôi nhận được lỗi: java -jar hellow.jar Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject \t tại java.lang.ClassLoader.defineClass1 (Native Method) \t tại java.lang.ClassLoader.defineClass (ClassLoader.java: 676) \t tại java.security.SecureClassLoader.defineClass (SecureClassLoader.java:124) \t tại java.net.URLClassLoader.defineClass (URLClassLoader.java:260) . Cấu trúc cây: http://pastebin.com/3k8rCpxf – I82Much

+0

@ I82Much - Tôi không thấy bất kỳ vấn đề rõ ràng nào với cấu trúc cây của bạn; Tôi muốn unjar hellow.jar và xác minh rằng tệp kê khai có phụ thuộc đúng. – McDowell

+0

gần đây tôi đã đi qua vấn đề tương tự - chỉ khi tôi viết đường dẫn tuyệt đối cho thư viện trong vấn đề Class-Path biến mất (~ diddn't làm việc) – xhudik

9

Vì Scala kịch bản đòi hỏi các thư viện Scala phải được cài đặt, bạn sẽ phải bao gồm thời gian chạy Scala cùng với bạn JAR.

Có nhiều chiến lược để thực hiện việc này, chẳng hạn như jar jar, nhưng cuối cùng vấn đề bạn thấy là quá trình Java mà bạn đã bắt đầu không thể tìm thấy các JAR Scala. Đối với một kịch bản độc lập đơn giản, tôi khuyên bạn nên sử dụng jar jar, nếu không bạn nên bắt đầu tìm kiếm công cụ quản lý phụ thuộc hoặc yêu cầu người dùng cài đặt Scala trong JDK.

+0

Có, đóng gói lại các lọ không phải là vấn đề từ quan điểm cấp phép: http://www.scala-lang.org/node/146 – McDowell

4

Bạn cũng có thể sử dụng maven và maven-scala-plugin. Một khi bạn thiết lập maven, bạn chỉ có thể làm mvn gói và nó sẽ tạo ra jar của bạn cho bạn.

+1

JAR được tạo ra không bao gồm scala-library.jar. –

+2

@simon - điều này phụ thuộc vào bạn để thêm thư viện scala vào các tệp phụ thuộc trong tệp pom – xhudik

2

Tôi đã sửa đổi tập lệnh bash thêm một số thông tin tình báo bao gồm tạo tự động tạo tệp kê khai.

Tập lệnh này giả định rằng đối tượng chính được đặt tên giống với tệp trong (phân biệt chữ hoa chữ thường).Ngoài ra, tên thư mục hiện tại phải bằng tên đối tượng chính hoặc tên đối tượng chính sẽ được cung cấp dưới dạng tham số dòng lệnh. Khởi chạy tập lệnh này từ thư mục gốc của dự án của bạn. Sửa đổi các biến ở trên cùng theo yêu cầu.

Lưu ý rằng tập lệnh sẽ tạo thư mục thùng rác và thư mục dist và sẽ xóa mọi nội dung hiện có trong thùng rác.


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

Một điều mà có thể gây ra vấn đề tương tự (mặc dù nó không phải là vấn đề trong câu hỏi ban đầu ở trên) là Java vm dường như đòi hỏi phương pháp chính trả void. Trong Scala chúng ta có thể viết một cái gì đó tương tự (quan sát = -Đăng trong định nghĩa của chính):

object MainProgram { 

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

nơi chính thực sự trả về một -object GUI (tức là nó không phải void), nhưng chương trình sẽ chạy độc đáo khi chúng ta bắt đầu nó bằng lệnh scala.

Nếu chúng tôi gói mã này vào tệp jar, với MainProgram làm lớp chính, Java vm sẽ khiếu nại rằng không có chức năng chính, vì loại trả về của chính chúng tôi không phải là void (Tôi thấy khiếu nại này phần nào lạ, vì kiểu trả về không phải là một phần của chữ ký).

Chúng tôi sẽ không gặp vấn đề gì nếu chúng tôi bỏ qua dấu = trong tiêu đề chính hoặc nếu chúng tôi tuyên bố rõ ràng là Unit.

5

Tôi đã sử dụng sbt assembly, nó thực sự đơn giản để sử dụng. Tôi đã thêm một tệp có tên là assembly.sbt vào thư mục project/ ở gốc của dự án với một lớp lót (Lưu ý rằng phiên bản của bạn có thể cần được thay đổi).

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

Sau đó chỉ cần chạy các nhiệm vụ assembly trong sbt:

> assembly 

Hoặc chỉ cần 'SBT lắp ráp' trong thư mục gốc của dự án

$ sbt assembly 

đầu tiên Nó sẽ chạy thử nghiệm của bạn và sau đó nó sẽ tạo lọ mới vào thư mục target/ (với điều kiện là build.sbt của tôi đã liệt kê tất cả các phụ thuộc của tôi).

Trong trường hợp của tôi, tôi chỉ làm cho tệp thực thi .jar, đổi tên để xóa tiện ích và đã sẵn sàng gửi!

Ngoài ra, nếu bạn đang thực hiện công cụ dòng lệnh, đừng quên thêm man page (Tôi ghét tập lệnh không có trang mano hoặc tài liệu văn bản thuần tuý nhiều trang thậm chí không được đưa vào máy nhắn tin cho bạn).

3

Tôi đã cố gắng tạo lại phương thức MyDowell. Cuối cùng tôi có thể làm cho nó hoạt động. Tuy nhiên tôi thấy rằng câu trả lời mặc dù chính xác một chút quá phức tạp cho một người mới (đặc biệt là cấu trúc thư mục là không cần thiết phức tạp).

Tôi có thể tạo lại kết quả này bằng các phương tiện rất đơn giản. Để bắt đầu chỉ có một thư mục chứa ba tệp:

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 

đầu tiên helloworld.scala biên dịch:

scalac helloworld.scala 

sau đó tạo ra các jar:

\progra~1\java\jdk18~1.0_4\bin\jar -cfm helloworld.jar MANIFEST.MF . 

bây giờ bạn có thể chạy nó với :

java -jar helloworld.jar 

Tôi tìm thấy giải pháp đơn giản này vì giải pháp ban đầu không hoạt động. Sau đó tôi phát hiện ra rằng không phải vì nó là sai, nhưng vì một lỗi nhỏ: nếu tôi không đóng dòng thứ hai trong MANIFEST.MF với một dòng mới, sau đó dòng này sẽ bị bỏ qua. Điều này đã cho tôi một giờ để tìm hiểu và tôi đã thử tất cả những thứ khác trước đây, trong quá trình tìm kiếm giải pháp này rất đơn giản.

3

Tôi không muốn viết lý do tại sao và làm thế nào ấy chứ không chỉ hiển thị các giải pháp mà làm việc trong trường hợp của tôi (thông qua dòng lệnh 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 
0

Nếu bạn không muốn sử dụng cơ sở vật chất SBT tôi khuyên bạn nên sử dụng một makefile.

Dưới đây là ví dụ trong đó gói foo được thay thế bằng foo.bar.myApp để hoàn thành.

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 
Các vấn đề liên quan