Create a jar with dependencies from bash - java

First of all I want to say that I do not want to use Maven, Ant, Gradle, any IDE or similar for this task.
I want to make a runnable jar using a bash script. The jar should includes all jar-content from my "libs" folder.
I have started this script and it "almost" works(until the jar creattion):
#!/bin/bash
#Setup
libs=$(find libs -name "*.jar" -print0 | xargs -0 | sed -e 's/ /:/g')
tmp2=$(sed -e 's/:/\n ..\//g' <<< $libs)
tmp2=../$tmp2
libs=$libs:src
mkdir tmp
#Compile
javac -d tmp -classpath "$libs" src/main/PublishData.java
#Create manifest
cd tmp
echo "Main-Class: main.PublishData" >> m.mf
echo "Class-Path: $tmp2" >> m.mf
#Build jar
jar cvfm thejar.jar m.mf *
An m.mf example file would look like this:
Main-Class: MyMain
Class-Path: ../libs/1.jar
../libs/2.jar
The file structure looks like this:
├── src
├── tmp (created by the script, jar cvfm thejar.jar m.mf * is run here)
├── libs
│ ├── jar1
│ ├── jar2
The jar file is created and can be executed but the referenced jars are not found.
(E.g: Exception in thread "main" java.lang.NoClassDefFoundError: com/mongodb/MongoClient
)
It seams to me like the jar program could not find the jars.
How can I successfully "include" my jars from the libs dir?
UPDATE:
Every line in the "Class-Path:" section of a manifest file needs to start and end with a white space this caused the class-path to break.

Your approach looks good in general.
Relative paths work in the manifest but you need to make sure that the paths are available at runtime. So if you say "load ../libs/foo.jar", then for the classloading to work, you need to be in the src/, tmp/ or libs/ when you start the application.
The main problem is that Java silently ignores JARs which it can't find. I hesitate to call this a feature, especially since so many people run into problems starting a Java application. So you will have to painstakingly find out what works and what doesn't.
To make things more simple, I suggest that you JAR all the classes of your project as well and then create a pure "classpath" JAR (i.e. no classes, just a manifest file with the classpath + the main class). That way, all your dependencies work the same.
Another thing you should try is to write a small project (just the hello world kind) where you can test your "create manifest JAR" script.
That said, my approach is to create a main class which accepts a path as arguments (the absolute path to the libs folder). It then lists all the JAR files in the folder (plus some debug output so I can see what it finds) and creates a new URLClassLoader with those URLs. That way, I can debug classpath issues easily plus my "start" script work like this:
BASE=$(cd $( dirname "$0" ) > /dev/null ; pwd )
java -jar $BASE/starter.jar $BASE/libs

Related

External Jars in VS code

I have a program written in java using Eclipse. For some reason that I won't write here, I decided to move to VS Code. If I run my code in debug mode, all works, but, when I want to export as jar file some errors comes out.
Some information:
- The program is composed by several classes.
- I use 3 external jars included via Eclipse.
- If I run the code with the extension 'Java extension pack - microsoft' all works. Compiling via terminal with
javac MyApp.java
it doesn't compile. (It doesn't find some classes belonging to external jars)
- If I use
jar -cvfm MyApp.jar manifest.txt *.class
where *.class are created by compiling via 'Java extension pack' the error is 'Unable to find or load the main class'
- I'm using a MacBook Pro and the last version of VS Code
What do I do wrong? Which more information you need to help me?
Let's say your project has app package. Under that a App.java class resides which has the main method. Now after building the class files let's assume the class file folder structure is
bin
|app
|App.class
Now go to the bin folder and copy the manifest.txt file in bin folder. manifest.txt file must contain Main-Class . here app.App is the name of the Main-Class.
Main-Class: app.App
Note manifest.txt file must be ended with a new line or carriage return . After Main-Class: app.App put a new line at least.Now run this command from the bin folder
jar cfmv App.jar manifest.txt app/
then test the Jar with
java -jar App.jar

jar throwing NoClassDefFoundError

I've got a project that is structured liked so:
root/
-- lib/
---- commons-cli-1.2.jar
-- src/
---- my/package/name/*.java
-- bin/
---- my/package/name/*.class
-- .classpath
-- .project
-- manifest.mf
The *.class files in bin/ are made by Eclipse each build. My .classpath has the lib/ included and compiles just fine; it also runs as a "Java Application" just fine with my current stubs.
The issue comes about when I try to create a JAR and include the lib/ dependencies. From the command line I've been issuing:
jar cvfm prog.jar manifest.mf -C bin/ .
The program builds, and then when I try to run java -jar prog.jar, I get:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/cli/Option... (there is more, but I cant copy from the other machine)
My manifest.mf looks like:
Manifest-Version: 1.0
Main-Class: my.package.Main
Class-Path: ./lib/commons-cli-1.2.jar
Seems to me that the Class-Path isn't being included and the JAR cannot find the classes contained in the commons-cli-1.2.jar. I've isolated this to JAR creation, since I can run the compiled classes with no issue.
What am I doing wrong when creating the JAR and including the lib/*.jar?
I have had issues with specifying classpath in the manifest file before. If I were you, I would skip referencing the required libraries in the manifest file and instead include them directly inside your jar. Eclipse allows you to easily do this link
You probably assume that the build process should pull the commons JAR into the new that holds your classes. Wrong assumption.
The default Java approach is that you only include your classes in your jar. But instruct your users that they need to have the commons jar in their class path as well!

Is it possible to have a jar Manifest to use all jars in a folder

I'm trying to set a jar manifest so it loads all the libraries (jars) within a folder next to the jar.
The manifest looks like this:
Manifest-Version: 1.0
Class-Path: libs/
Main-Class: path.to.my.class.Main
The layout is as follows:
- MyJar.jar
- libs/
-----lib1.jar
-----lib2.jar
And I'm trying to run like this:
java -jar MyJar.jar
And I get NoClassDefinition errors about classes in the jar within the folder.
In case someone is curious, this folder might contain optional jars that are processed during class loading. That' swhy I can't use the hardcoded or autogenerated ones.
Any idea?
Update
Rephrased the question as this is not currently possible from the manifest itself. The answer was the only really viable without the need of extracting the jars, although it also works.
So as a summary the answer is no, this can't be done from manifest file alone if you have unespecified dependencies.
You should define your Manifest classpath as
Class-Path: libs/lib1.jar libs/lib2.jar
See Oracle documentation for more details https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html
Try extracting these jars. It looks like you cannot add all jars from directory but you can add all classes. You lose obviously all configuration in manifest, however, if you are interested in jars' code content only, it might work.
I tested that with these simple classes
import pkg.B;
public class A {
public static void main(String[] args) {
System.out.println(B.class.getName());
}
}
package pkg;
public class B {}
now I try to separate the classes. I have jarred them into
$ jar tf libA.jar
META-INF/
META-INF/MANIFEST.MF
A.class
$ jar tf libB.jar
META-INF/
META-INF/MANIFEST.MF
pkg/B.class
no Class-Path in any manifest. I can run A with java -cp libB.jar:libA.jar A. Now I create another jar with Class-Path set to lib/
$ cat manifest
Class-Path: lib/
$ jar cfm empty.jar manifest
my directory tree look like
$ ls -R
.:
A.java empty.jar lib lib.jar manifest pkg
./lib:
libA.jar libB.jar
./pkg:
B.java
Now I try jar
$ java -jar empty.jar
Error: Could not find or load main class A
Hopeless, right? Then I extracted libA.jar and libB.jar into lib (same as [this guy][2]). Now all is fine
$ java -jar empty.jar
pkg.B
java does not know the jar files in the libs directory.
If you are using java 1.6+, You can run program as
java -cp lib/* -jar MyJar.jar

How to make jar file?

Folder with program:
C:\ProgramFolder
In folder there is a package with classes:
com\mysite\my_program
Main class name:
Program.class
... or fullname:
com.mysite.my_program.Program
In the root folder there is manifest file with content:
Manifest-Version: 1.0
Main-Class: com.mysite.my_program.Program
And I'm making jar-file:
jar cmf MANIFEST.MF my_program.jar com\mysite\my_program\*.class
and trying to lunch it:
java -jar my_program.jar -a -b -c http://google.com/ google
this:
-a -b -c http://google.com/
are paramerts to my program. Tesult is the same without them.
And I'am getting error:
Couldn't find or load main class Цjar
In real program manifest file looks like this:
So, I don't know why Цjar
In C:\ProgramFolder create a subdirectory called META-INF and move the MANIFEST.MF into that, recreate your jar file. and try again.
EDIT
actually the jar m will add that into the right location, but you are missing Class-Path:
Use e (entrypoint) that way you may create a jar without adding manifest file.
jar -cfe my_program.jar com.mysite.my_program.Program.class
or
jar -cfe my_program.jar com/mysite/my_program/Program.class
and launch the program:
java -jar my_program.jar -a -b -c "http://google.com/ google"
There is a standard way to create packages in Java.
Using command like this :
javac -d . *.java
But if you will simply make Folders and then treat it as packages it will not work.
Try making proper packages.
NetBeans id generate jar file automatically. You have no need to extra work for jar file. You have to just copy the project in NetBeans and compile it.

How to make javac find JAR files? (Eclipse can see them)

When I'm in Eclipse my project compiles with no errors, however when I try to compile with javac it says I'm missing some packages...
I copied my compile command and some of the error text below:
javac -classpath lib/ -d bin/ src/*.java
src/Cleaner.java:5: package net.sourceforge.jgeocoder does not exist
src/MyUtilities.java:19: package org.apache.commons.codec.binary does not exist
In Eclipse, I have added all the .JAR files to the build-path, and the program compiles just fine.
Why can it not find the jars when I use javac instead of the Eclipse IDE?
-classpath lib/ will cause javac to look for a tree of class files in lib. If you have JAR archives there, you have to use -classpath lib/*.jar - and probably use whatever escaping mechanism your CLI has on the * to make sure it reaches javac rather than being expanded by the CLI
See the javac command reference (windows).
Existing answer, as informative as it is, still left me wondering, having to go over the javac -classpath docs. Here's a revision of what you might want to know:
javac -cp "lib/" Example.java will load all .class files in lib/.
javac -cp "lib/\*" Example.java will load all .jar files in lib/, but not the .class files.
javac -cp "lib/;lib/\*" Example.java will load all .jar and .class files in lib/.
Notes:
The * (wildcard) character as used in above examples is escaped (note the \ before *). \ is for bash, if you're using something else it might be different. You want to escape it because the java sdk utilities, like javac, their own way of interpreting the wildcard. As far as I can tell (correct me if I'm wrong), Windows command line doesn't expand * so you don't need to escape it: -cp lib\* should be fine.
; separates paths, as in javac -cp ".;a/;b/;c/" Example.java – this would load .class files in the following directories: current directory, a, b and c.
Surround the classpath with quotes (like in examples) when using separators (;).
Just adding to the existing answers to clarify the case when using #argfiles.
Assume you have the following project structure:
MyProject
├── lib
│   ├── lib1.jar
│   ├── lib2.jar
│   └── speciallib
│   ├── speciallib1.jar
│   └── speciallib2.jar
├── out
└── src
└── MyClass.java
If you were to compile this on the command line (e.g. using linux path separators), you can say
javac -d out -sourcepath src -classpath 'lib/*:lib/speciallib/*' src/MyClass.java
However, if you wanted to create an #argfile to keep things tidy, then the '*' syntax will not work. You'll need to specify all .jar files in your classpath individually. So the #argfile for this would look like this:
-d out
-sourcepath src
-classpath '\
lib/lib1.jar:\
lib/lib2.jar:\
lib/speciallib/speciallib1.jar:\
lib/speciallib/speciallib2.jar\
'
src/MyClass.java
which you would then run like this:
javac #argfile
Thankfully, it is not too difficult to dump all .jar files into your file in an automated manner. E.g. an easy way on linux is to use the find command (also passing to sed to append a colon path separator and a backslash for line continuation) and append this to your argfile. E.g.
find lib -name "*.jar" | sed "s/\$/:\\\\/" >> argfile

Categories