first create a file .mvn/extensions.xml
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>io.takari.polyglot</groupId>
<artifactId>polyglot-ruby</artifactId>
<version>0.1.7</version>
</extension>
</extensions>
which tells mvm 3.3.1+ to use the polyglot ruby extension which is a ruby DSL for maven.
this blog will show how to setup a java library using ruby gems and a ruby library (a ruby gem) which uses java libraries (jar files).
just create an empty Mavenfile and put you code in src/main/java and execute
mvn package
Mavenfile is borrowed from the ruby world where there are Gemfile (from bundlder) or Jarfile (from jbundler) and there are others: Guardfile, etc. this Mavenfile is the same as the pom.rb and uses exactly the same DSL. here I stick to the Mavenfile and to a more ruby like notation, i.e. an empty Mavenfile is enough to get started.
to control the groupId, artifactId and version add
id 'com.example:app:1.0.0'
now let's add a test setup to it
scope :test do
jar 'junit:junit:4.11'
end
phase :test do
plugin :surefire, '2.12.4' do
execute_goal :test, :forkMode => :always
end
end
the test jar is needed to compile the test classes and we add some configuration to the surefire plugin. all regular maven plugins can use such a shortcut. the full form of the surefire
plugin is
plugin 'org.apache.maven.plugins', 'maven-surefire-plugin', '2.12.4' do
execute_goal :test, :forkMode => :always
end
the phase or scope can be also declared in the maven way:
jar 'junit:junit:4.11', :scope => :test
plugin :surefire, '2.12.4' do
execute_goal :test, :forkMode => :always, :phase => :test
end
one extra feature of the ruby dsl is to reopen the plugin again in different location of file:
phase :test do
plugin :surefire, '2.12.4' do
execute_goal :test, :forkMode => :always
end
end
phase 'integration-test' do
plugin! :surefire do
execute_goal :test, :id => 'its', :forkMode => :always
end
end
the bang at the plugin means that the plugin setup gets modified and add the extra execute goal to former plugin declaration. this allows to order you execute_goal
per phase.
if your application needs rubygems, i.e. is using org.jruby.embed.ScriptingContainer
. suppose you need maven-tools.gem and you have some ruby scripts in src/main/ruby then add the following to your Mavenfile
repository :id => 'rubygems-releases', :url => 'http://rubygems-proxy.torquebox.org/releases'
plugin_repository :id => 'sonatype-snapshots', :url => 'https://oss.sonatype.org/content/repositories/snapshots'
scope :provided do
gem 'maven-tools', '1.0.8'
end
phase :initialize do
jruby_plugin :gem, '1.0.10-SNAPSHOT' do
execute_goal :initialize, :includeProvidedRubygemsInResources => true
end
end
phase 'process-resources' do
jruby_plugin! :gem do
execute_goal 'process-resources', :id => 'jrubydirs'
end
end
the torquebox repository is for the gem-artifacts which provides a maven gem-artifact for all gems from rubygems.org which are compatible with JRuby.
'initialize' goal of gem plugin installs the gems into the working directory and the 'process-resources' goal will add directory info files .jrubydir in each directory which are needed whenever JRuby runs on a classloader which is not a URLClassLoader
. more details on this here: http://aadesa.blogspot.co.uk/2014/12/improvements-of-jruby-java-integration.html
the jruby_plugin declaration is a shortcut for plugin 'de.saumya.mojo', 'gem-maven-plugin', '1.0.9'
and a similar shortcut works for all https://github.com/torquebox/jruby-maven-plugins.
again the jruby_plugin! declaration reopens a previous declaration of the same plugin (or creates a new one).
actually the plugin declaration can be rewritten as
jruby_plugin :gem, '1.0.10-SNAPSHOT' do
execute_goal :initialize, :includeProvidedRubygemsInResources => true
execute_goal 'process-resources', :id => 'jrubydirs'
end
the splitting over the phases is just for illustrative purposes.
for an quick overview of the ruby dsl have a look at (all three pom.rb files are equivalent): https://github.com/torquebox/maven-tools/tree/master/spec/pom_maven_style https://github.com/torquebox/maven-tools/tree/master/spec/pom_maven_hash_style https://github.com/torquebox/maven-tools/tree/master/spec/pom_maven_alternative_style
let's have a example.gemspec with this content
Gem::Specification.new do |s|
s.name = 'example'
s.version = '123'
s.summary = 'example gem'
s.description = 'example gem'
s.homepage = 'http://example.com'
s.authors = ['Christian Meier']
s.email = ['[email protected]']
s.license = 'MIT'
end
now you can build the gem with maven:
mvn package
which is the same as gem build example.gemspec
. you can push it to rubygems.org (as gem push example-123.gem
) with
mvn deploy
the install phase is installing a gem-artifact to the local repo. the whole thing works as long all gem dependencies of the gemspec are working for the java platform (or jruby platform as bundler calls it).
note that the installed pom of gem-artifact is different from the pom which you are running. it is essentially the same as the pom files from http://rubygems-proxy.torquebox.org/releases/rubygems/. the reason is to keep the installed poms in sync whether they come from a local install or from a remote repository.
just add your sources to src/main/java and create a jar file for the gem:
mvn prepare-package
this compiles the java sources and creates a lib/example.jar. within (j)ruby you can just require 'example.jar'
to use it.
note packing the jar is done in prepare-package phase since the actual package is to build/package the gem file which will contain the jar file.
if you want junit tests for your java code then put your test classes in src/test/java. now customize things with a Mavenfile like this:
gemspec
jar 'junit:junit:4.11', :scope => :test
plugin :surefire, '2.12.4' do
execute_goal :test, :forkMode => :always, :phase => :test
end
mvn will look first look for a 'Mavenfile' then 'pom.rb', 'Gemfile' and finally for a gemspec file. in this case the Mavenfile has a directive to use the gemspec file as well.
with this
mvn test
runs the surefire plugin with the junit tests.
in case you want stay inside the ruby-world not adding another commandline tool then just have a Rakefile with
require 'maven/ruby/maven'
mvn = Maven::Ruby::Maven.new
desc "Package example.jar with the compiled classes"
task :jar do
mvn.prepare_package '-Dmaven.test.skip'
end
desc "run junit tests from src/test/java"
task :junit do
mvn.test
end
and add
s.add_development_dependency 'ruby-maven'
to the gemspec. now you can build the jar with
bundle -S rake jar
or run the junit test
bunlder -S rake junit
and to add a tasks which builds the gem either the maven way or ruby is straight forward.
now have example.gemspec with some dependencies:
Gem::Specification.new do |s|
s.name = 'example'
s.version = '123'
s.summary = 'example gem'
s.description = 'example gem'
s.homepage = 'http://example.com'
s.authors = ['Christian Meier']
s.email = ['[email protected]']
s.license = 'MIT'
s.add_runtime_dependency 'leafy-metrics', '~> 0.2'
s.add_runtime_dependency 'jar-dependencies', '~> 0.1.0'
s.requirements << 'jar org.slf4j:slf4j-simple, 1.7.12, :scope => :test'
s.requirements << 'jar com.fasterxml.jackson.core:jackson-databind, 2.4.2'
end
you can have a look at the dependency tree
mvn dependency:tree
which looks like this
rubygems:example:gem:123
+- rubygems:leafy-metrics:gem:0.3.0:compile
| +- io.dropwizard.metrics:metrics-core:jar:3.1.0:compile
| \- io.dropwizard.metrics:metrics-graphite:jar:3.1.0:compile
+- rubygems:jar-dependencies:gem:0.1.10:compile
+- rubygems:ruby-maven:gem:3.1.1.0.11:test
| +- rubygems:maven-tools:gem:1.0.8:test (version selected from constraint [1.0.8,1.0.99999])
| | \- rubygems:virtus:gem:1.0.4:test (version selected from constraint [1.0,1.99999])
| | +- rubygems:descendants_tracker:gem:0.0.4:test (version selected from constraint [0.0.3,0.99999])
| | | \- rubygems:thread_safe:gem:0.3.5:test (version selected from constraint [0.3.1,0.99999])
| | +- rubygems:equalizer:gem:0.0.9:test (version selected from constraint [0.0.9,0.99999])
| | +- rubygems:coercible:gem:1.0.0:test (version selected from constraint [1.0,1.99999])
| | \- rubygems:axiom-types:gem:0.1.1:test (version selected from constraint [0.1,0.99999])
| | \- rubygems:ice_nine:gem:0.11.1:test (version selected from constraint [0.11.0,0.11.99999])
| \- rubygems:ruby-maven-libs:gem:3.1.1:test (version selected from constraint [3.1.1,3.1.1.0.0.0.0.1))
+- junit:junit:jar:4.11:test
| \- org.hamcrest:hamcrest-core:jar:1.3:test
+- org.slf4j:slf4j-simple:jar:1.7.12:test
| \- org.slf4j:slf4j-api:jar:1.7.12:compile
\- com.fasterxml.jackson.core:jackson-databind:jar:2.4.2:compile
+- com.fasterxml.jackson.core:jackson-annotations:jar:2.4.0:compile
\- com.fasterxml.jackson.core:jackson-core:jar:2.4.2:compile
you see both the gem as well the jar dependencies.
alternatively you could put the jar dependencies into the Mavenfile and then the dependency tree will look the same. but there is an important difference when putting the jar dependencies into the gemspec and add the jar-dependencies
gem as dependency as well. assume you have jruby install
jruby -S gem install bundler
jruby -S bundle install
this will resolve and install the gem dependencies with bundler BUT also resolve and download the jar dependencies using maven with the same ruby dsl under the hood. further this bundle install will create a file lib/example_jars.rb which has some require_jar statements for the resolved jars. this file creation happens wheneven you install the gem either via bundler or rubygems and recreate this lib/example_jars.rb so this file is in sync with resolved dependencies.
note that the jar-dependencies gem is important for triggering the jar dependencies resolution with ruby tools. and maven itself will not generate the lib/example_jars.rb (this might change in future).
please see all the directories with gemspec at the beginning of their name: https://github.com/torquebox/maven-tools/tree/master/spec/
enjoy and please file any potential issue on https://github.com/takari/maven-polyglot/issues