Skip to content

Instantly share code, notes, and snippets.

@mkristian
Last active August 29, 2015 14:18
Show Gist options
  • Save mkristian/1c59196ae9b442046d62 to your computer and use it in GitHub Desktop.
Save mkristian/1c59196ae9b442046d62 to your computer and use it in GitHub Desktop.
ruby dsl blog2

Ruby DSL with Maven

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).

java application

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.

java application with embedded ruby gems

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.

overview of maven ruby dsl

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

ruby gem

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.

ruby gem with java sources

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.

ruby gem with jar dependencies

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).

overview of the gemspec deirective

please see all the directories with gemspec at the beginning of their name: https://github.com/torquebox/maven-tools/tree/master/spec/

meta-fu

enjoy and please file any potential issue on https://github.com/takari/maven-polyglot/issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment