JRuby, Warbler, and Java's CLASSPATH - java

I've been developing applications in JRuby lately and really enjoying it, but I've been running into a wall when it comes to packaging my project into a JAR file when it includes external Java libraries. If the project does not depend on any external Java library JAR files, I run into no problems.
Below is an example application. This code works perfectly fine when running the ./bin/my_proj executable. But, when I package it into a JAR file, the external Java library cannot be loaded because it is not found on the CLASSPATH.
When I unpackage my application's JAR file, I can see that it includes all of my code as well as the vendor directory containing the external Java library. So, everything's where it should be.
lib/my_proj/application.rb
java_import 'com.somecompany.somejavalibrary.SomeJavaLibraryClass'
module MyProj
class Application < SomeJavaLibraryClass
# Some code implementing SomeJavaLibraryClass
end
end
lib/my_proj.rb
require 'pathname'
module MyProj
def root
Pathname.new(__FILE__).join('..', '..').expand_path
end
def start
setup_environment
Application.new
end
def setup_environment
#setup ||= false
unless #setup
#setup = true
require 'java'
$CLASSPATH << root.join('vendor').to_s # Setup Java CLASSPATH
$LOAD_PATH << root.join('lib').to_s # Setup Ruby LOAD_PATH
require 'some_java_library' # Load the external Java library from it's JAR
require 'my_proj/application'
end
end
extend self
end
bin/my_proj
#!/usr/bin/env ruby
$:.unshift File.expand_path( File.join('..', '..', 'lib'), __FILE__ )
require 'my_proj'
MyProj.start
config/warble.rb
Warbler::Config.new do |config|
config.features = %w(gemjar compiled)
config.autodeploy_dir = 'pkg'
config.dirs = %w(assets bin config lib)
config.java_libs += FileList['vendor/*.jar']
end
vendor/some_java_library.jar
# This is the external Java library

The external jars should be in the lib folder.
You can add them in code by doing something like
$CLASSPATH << "vendor/some_java_library.jar" #or loop the directory for all jars and add them
Or you can create a META-INF/MANIFEST.MF file and specifiy the CLASSPATH jars
and adding a line like
Class-Path: vendor/some_java_library.jar jar2-name directory-name/jar3-name
http://docs.oracle.com/javase/tutorial/deployment/jar/downman.html

Related

Running an uber jar from sbt assembly results in error: Could not find or load main class

I have a spark job packaged as an uber-jar using the sbt assembly plugin.
The build.sbt specifies a runnable main to be the target of the resulting uber-jar
mainClass in assembly := Some("com.foo.Bar")
After the assembly is correctly created, running the intended command:
java -jar assembly.jar
results in
Error: Could not find or load main class com.foo.Bar
Using the an alternative method, like java -cp assembly.jar com.foo.Bar gives the same error message.
Then, I extracted the contents of the uber-jar in a new directory. I can see my com/foo/ directory and the Bar.class file.
From the root of the extracted directory I tried:
java -cp . com.foo.Bar
and I get a correct result.
Further trying to find the reason of the error, I tried:
java -verbose -jar assembly.jar
I can see the java core classes being loaded, but I don't see any of my packaged classes being loaded.
What can possibly be wrong here?
After an extensive investigation (read: pulling hairs out), it turns out that this behavior is the result of a rogue INDEX.LIST from one of the flattened jar files landing in the META-INF directory of the resulting uber-jar.
Following the JAR file spec, the INDEX.LIST, if present, dictates what packages from the Jar file are to be loaded.
To avoid this, we updated the mergeStrategy with a rule to avoid any pollution of the resulting META-INF directory:
case PathList("META-INF", xs # _*) => MergeStrategy.discard
This fixed the issue and returned my sanity.
Update:
After some extra searching, it turns out that the default merge strategy takes proper care of INDEX.LIST. This answer applies when the customized merge strategy contains cases that handle the META-INF pathSpec

Jruby trying to load local ruby gems instead of packaged ones

I'm seeing a weird issue in my jruby jar and I'm sure it's due to some configuration but I can't quite figure out what. The behavior I'm seeing is when my jruby jar goes to load a gem it looks for it in my local ruby envrionment instead of inside the jar itself. If I move my jar to a system that does not have a ruby envrionment then it uses the gems packaged in the jar.
Here is my boot rb file.
require 'rubygems'
app_jar_root = File.expand_path(File.join(File.dirname(__FILE__), ".."))
Dir["#{app_jar_root}/Project/**/*/"].each do |foldername|
$LOAD_PATH.unshift foldername[-1] == '/' ? foldername[0..-2] : foldername
end
# All support libraries required to be included
[
'java',
'yaml'
].each do |require_name|
require require_name
end
# All java imported namespaces
[
# 'java.sql.DriverManager'
].each do |namespace|
java_import namespace
end
# base app directory requires
Dir["#{app_jar_root}/Project/app/**/*.rb"].reject do |filename|
%w|file_to_exclude1.rb file_to_exclude2|.include? File.basename(filename)
end.each do |filename|
require File.basename(filename)
end
# Debugger.start
I can also post my warble.rb if that would be of any use but I'm assuming the issue is some configuration in boot.rb.
Turns out the issue was that my jruby jar was referencing the local GEM_PATH and GEM_HOMEvariables if they existed on a system. To fix this I had to redirect the variables to point inside the jar by reassigning them in the boot.rb file.
Here's an example of how I achieved this where app_jar_root is the root of the app folder in the jar.
ENV['GEM_HOME'] = "#{app_jar_root}/gems"
ENV['GEM_PATH'] = "#{app_jar_root}/gems"

j2objc Xcode build rules, not recognizing imports

I am using j2objc to compile and use a java library in iOS. I followed the processes:
http://j2objc.org/docs/Xcode-Build-Rules.html
http://j2objc.org/docs/Required-Link-Flags.html
I do not get any build errors until I start importing the header files to use them:
#import "org/library/Class.h"
The files are not found. What I am missing?
On the other hand, I tried to use the manually translated library files (using the terminal j2objc commands). If I pùt the .h and .m files into the j2objc-dist/include folder they are recognized by the editor and I can use the classes without compile errors. But, when I try to build the project it finds errors of the type _OBJ_CLASS_$_Java. I tried to include the files to the compile list in Xcode and I verified the path of libjre_emul.a but I still get the error.
My library contains packages so it has multiple folders in a tree.
My preference will be to use the first method (original Java sources)
DATA FOR JAVA SOURCES CASE:
Build rules:
Java source files
Custom script:
/Users/xgibert/Desktop/Orekit_iOS/j2objc-dist/j2objc -d ${DERIVED_FILES_DIR} -sourcepath ${PROJECT_DIR}/src/ ** \ --no-package-directories ${INPUT_FILE_PATH};
Output files:
${DERIVED_FILES_DIR}/${INPUT_FILE_BASE}.h
${DERIVED_FILES_DIR}/${INPUT_FILE_BASE}.m
Build phases
Link Binary with libraries:
libicucore.dylib
Security.framework
libz.dylib
Build Settings
Linking - Other Linker Flags:
-ljre_emul -L /Users/xgibert/Desktop/Orekit_iOS/j2objc-dist/lib -force_load /Users/xgibert/Desktop/Orekit_iOS/j2objc-dist/lib/libjre_emul.a -l jre_emul -ObjC
Search Paths - Library Seargh Paths:
/Users/xgibert/Desktop/Orekit_iOS/j2objc-dist/lib
Search Paths - User Header Search Paths:
/Users/xgibert/Desktop/Orekit_iOS/j2objc-dist/include
My java library files are in Project_root/src. The tree looks like this:
root/
src/
org/
orekit/
data/
time/
...
apache/
...
In my ViewController.m file I try to import with the following line without success (file not found):
#import "org/orekit/data/DataProvidersManager.h"
Xcode assumes all sources are in a top-level directory, so imports such as you describe fail. As described in the Xcode-Build-Rules page, the --no-package-directories flag is needed, as it outputs all generated files to the specified build directory without sub-directories, and generates correct #import directives.
A sample project that demonstrates including Java sources with packages is j2objc-sample-reversi. Its UI sucks (I wrote it, sigh), but the game engine is decent; if you need a tougher opponent, crank up the engine strength here.

Reference jars inside a jar

I have a jar whose content looks as shown below,
Below is my manifest file
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.7.0_06-b24 (Oracle Corporation)
Main-Class: org.sai.com.DerbyDemo
Class-Path: derby.jar derbyclient.jar derbynet.jar derbytools.jar
When i try to run the jar, it has thrown a ClassNotFoundExcception meaning it isn't referencing the jars inside the outer jar.
In the Class-Path attribute, how can I reference jars (derby.jar, etc) inside the actual jar?
You will need a custom class loader for this, have a look at One Jar.
One-JAR lets you package a Java application together with its dependency Jars into a single executable Jar file.
It has an ant task which can simplify the building of it as well.
REFERENCE (from background)
Most developers reasonably assume that putting a dependency Jar file into their own Jar file, and adding a Class-Path attribute to the META-INF/MANIFEST will do the trick:
jarname.jar
| /META-INF
| | MANIFEST.MF
| | Main-Class: com.mydomain.mypackage.Main
| | Class-Path: commons-logging.jar
| /com/mydomain/mypackage
| | Main.class
| commons-logging.jar
Unfortunately this is does not work. The Java Launcher$AppClassLoader does not know how to load classes from a Jar inside a Jar with this kind of Class-Path. Trying to use jar:file:jarname.jar!/commons-logging.jar also leads down a dead-end. This approach will only work if you install (i.e. scatter) the supporting Jar files into the directory where the jarname.jar file is installed.
You can't. From the official tutorial:
By using the Class-Path header in the manifest, you can avoid having
to specify a long -classpath flag when invoking Java to run the your
application.
Note: The Class-Path header points to classes or JAR files on the
local network, not JAR files within the JAR file or classes accessible
over internet protocols. To load classes in JAR files within a JAR
file into the class path, you must write custom code to load those
classes. For example, if MyJar.jar contains another JAR file called
MyUtils.jar, you cannot use the Class-Path header in MyJar.jar's
manifest to load classes in MyUtils.jar into the class path.
In Eclipse you have option to export executable jar.
You have an option to package all project related jars into generated jar and in this way eclipse add custom class loader which will refer to you integrated jars within new jar.
Default implementations of the classloader cannot load from a jar-within-a-jar: in order to do so, the entire 'sub-jar' would have to be loaded into memory, which defeats the random-access benefits of the jar format (reference pending - I'll make an edit once I find the documentation supporting this).
I recommend using a program such as JarSplice to bundle everything for you into one clean executable jar.
Edit: Couldn't find the source reference, but here's an un-resolved RFE off the Sun website describing this exact 'problem': http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4648386
Also, you could 'test' that your program works by placing the library jar files in a \lib sub-directory of your classes directory, then running from the command line. In other words, with the following directory structure:
classes/org/sai/com/DerbyDemo.class
classes/org/sai/com/OtherClassFiles.class
classes/lib/derby.jar
classes/lib/derbyclient.jar
From the command line, navigate to the above-mentioned 'classes' directory, and type:
java -cp .:lib/* org.sai.com.DerbyDemo
if you do not want to create a custom class loader. You can read the jar file stream. And transfer it to a File object. Then you can get the url of the File. Send it to the URLClassLoader, you can load the jar file as you want.
sample:
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("example"+ ".jar");
final File tempFile = File.createTempFile("temp", ".jar");
tempFile.deleteOnExit(); // you can delete the temp file or not
try (FileOutputStream out = new FileOutputStream(tempFile)) {
IOUtils.copy(resourceAsStream, out);
}
IOUtils.closeQuietly(resourceAsStream);
URL url = tempFile.toURI().toURL();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
urlClassLoader.loadClass()
...
Add the jar files to your library(if using netbeans) and modify your manifest's file classpath as follows:
Class-Path: lib/derby.jar lib/derbyclient.jar lib/derbynet.jar lib/derbytools.jar
a similar answer exists here
in eclipse, right click project, select RunAs -> Run Configuration and save your run configuration, this will be used when you next export as Runnable JARs

SCons for Java; Is there an analog for env.Program()?

I need to create an executable file, run, that will call # java for my classes
I am compiling my java project with SCons:
libFiles = "lib/myLibs.jar"
# Build the environment
env = Environment(JAVACLASSPATH = libFiles, JAVASOURCEPATH = '.')
env.Java(target = 'classes', source = 'src')
All of the classes are stored in folder classes/ and all the source files are in /src . To run the program, I have to
# cd classes/
# java -cp . myProg
Is there a way to have SCons create an executable in the root directory so it can call java by itself? I looked at an existing project that used env.Program() but that was only for C++.
Thanks!
You may use the Jar builder. The following SCons example does what you want.
jar = java_env = Jar(target='Observer',
source=['Observer.java',
'Manifest.txt'])
Note that if you want the Manifest.txt file to work as well it must have the following first line:
Manifest-Version: 1.0
You should only use the Java builder if you want to generate the .class files.

Categories

Resources