Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

java -jar rails.war -S rake db:migrate (and some cleanup) #130

Closed
wants to merge 13 commits into from

6 participants

Karol Bucek Guillermo Iguaran Josh Rendek Hiro Asari Ankur Sethi whitequark
Karol Bucek
Collaborator

I was working on it almost a year ago and got it to a place where rake seems to work :

kares@theborg:~/workspace/github/jruby-rack/examples/rails32$ java -jar rails32.war -S rake -T
rake about              # List versions of all Rails frameworks and the env...
rake assets:clean       # Remove compiled assets
rake assets:precompile  # Compile all the assets named in config.assets.pre...
rake db:create          # Create the database from DATABASE_URL or config/d...
rake db:drop            # Drops the database using DATABASE_URL or the curr...
rake db:fixtures:load   # Load fixtures into the current environment's data...
rake db:migrate         # Migrate the database (options: VERSION=x, VERBOSE...
rake db:migrate:status  # Display status of migrations
rake db:rollback        # Rolls the schema back to the previous version (sp...
rake db:schema:dump     # Create a db/schema.rb file that can be portably u...
rake db:schema:load     # Load a schema.rb file into the database
rake db:seed            # Load the seed data from db/seeds.rb
rake db:setup           # Create the database, load the schema, and initial...
rake db:structure:dump  # Dump the database structure to db/structure.sql. ...
rake db:version         # Retrieves the current schema version number
rake doc:app            # Generate docs for the app -- also available doc:r...
rake log:clear          # Truncates all *.log files in log/ to zero bytes
rake middleware         # Prints out your Rack middleware stack
rake notes              # Enumerate all annotations (use notes:optimize, :f...
rake notes:custom       # Enumerate a custom annotation, specify with ANNOT...
rake rails:template     # Applies the template supplied by LOCATION=(/path/...
rake rails:update       # Update configs and some other initially generated...
rake routes             # Print out all defined routes in match order, with...
rake secret             # Generate a cryptographically secure secret key (t...
rake stats              # Report code statistics (KLOCs, etc) from the appl...
rake test               # Runs test:units, test:functionals, test:integrati...
rake test:recent        # Run tests for {:recent=>"test:prepare"} / Test re...
rake test:single        # Run tests for {:single=>"test:prepare"}
rake test:uncommitted   # Run tests for {:uncommitted=>"test:prepare"} / Te...
rake time:zones:all     # Displays all time zones, also available: time:zon...
rake tmp:clear          # Clear session, cache, and socket files from tmp/ ...
rake tmp:create         # Creates tmp directories for sessions, cache, sock...
kares@theborg:~/workspace/github/jruby-rack/examples/rails32$ java -jar rails32.war -S rake db:migrate
==  DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
   -> 0.0200s
   -> 0 rows
-- add_index(:users, :email, {:unique=>true})
   -> 0.0050s
   -> 0 rows
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0050s
   -> 0 rows
==  DeviseCreateUsers: migrated (0.0360s) =====================================

there seems to be more work required for rails c to work :

kares@theborg:~/workspace/github/jruby-rack/examples/rails32$ java -jar rails32.war -S rails console
Usage:
  rails new APP_PATH [options]

Options:
  -r, [--ruby=PATH]              # Path to the Ruby binary of your choice
                                 # Default: file:/tmp/jruby7112847635427892118extract/lib/jruby-stdlib-1.7.1.jar!/META-INF/jruby.home/bin/jruby
...

I promised this on #128 thus here it is, hopefully others will find this useful even as is ...

Guillermo Iguaran
Collaborator

wow, this looks really great :+1:

Karol Bucek
Collaborator

probably having warble runnable war and warble executable war is a bit confusing ...
runnable is a .war that might -S rake db:migrate while executable is runnable + the embed web server currently
... better names would be to use executable for what is now "runnable" and add a separate webserver feature

whitequark

Simplecov is an 1.9 replacement for rcov.

Collaborator

Hey Peter, thanks I was not really worrying about coverage just to get things bundle-able ...
However, you do seem to have some useful commits in your fork.
Are you planning on opening a PR on upstream to (hopefully - one day) get them on naster ?

Collaborator

yeah, seems dead - but quite a few jruby projects would/might seems so ...
I would most certainly still open a PR - just in-case it breaths again, btw. which one is the most active, is it doing releases also ?

um. I meant your fork

Hiro Asari BanzaiMan was assigned
Josh Rendek

Thanks Asarih for looking into it :)

Hiro Asari BanzaiMan referenced this pull request from a commit in BanzaiMan/warbler
Hiro Asari BanzaiMan Based on #130, clean up Gemfile b24c924
Hiro Asari BanzaiMan referenced this pull request from a commit
Hiro Asari BanzaiMan Merge remote-tracking branch 'kares/runnable' into gh-130
Conflicts:
	.gitignore
	Gemfile
	ext/JarMain.java
	spec/sample_war/Rakefile
ff10266
Hiro Asari
Owner

Those of you who are interested in this PR, please test ff10266 (you can check out the gh-130 branch on this repo) as a proof of concept. I will look into the PR in more detail in coming days, but I'd want some early feedback as the patch stands right now.

Thank you.

Hiro Asari
Owner

I'm closing this one, since I opened #136. Please comment on that issue going forward.

Hiro Asari BanzaiMan closed this
Ankur Sethi

@kares I really appreciate the work on this. I had some ruby scripts to do this work and they were working ok, but did not work with the bundled git gems.

I am having a problem because I don't think the runnable is respecting:

config.webxml.jruby.compat.version

Can this be added please, I don't think it can be passed on the command line.

I am just going to make a new issue for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
1  .gitignore
View
@@ -4,3 +4,4 @@ doc
.bundle
nbproject
target
+Gemfile.lock
10 Gemfile
View
@@ -1,15 +1,11 @@
source "http://rubygems.org/"
-gem "rake"
-gem "rubyzip"
-gem "jruby-jars"
-gem "jruby-rack"
+gemspec
group :development do
gem "jruby-openssl", :platform => :jruby
- gem "rspec"
gem "diff-lcs"
- gem "rcov", ">= 0.9.8"
- gem "rdoc"
gem "childprocess", :platform => :mri
+ gem "rdoc"
+ gem "rcov", ">= 0.9.8", :platform => :mri_18
end
41 Gemfile.lock
View
@@ -1,41 +0,0 @@
-GEM
- remote: http://rubygems.org/
- specs:
- bouncy-castle-java (1.5.0146.1)
- childprocess (0.2.2)
- ffi (~> 1.0.6)
- diff-lcs (1.1.2)
- ffi (1.0.11)
- jruby-jars (1.6.7)
- jruby-openssl (0.7.6.1)
- bouncy-castle-java (>= 1.5.0146.1)
- jruby-rack (1.1.4)
- rake (0.9.2.2)
- rcov (0.9.10)
- rcov (0.9.10-java)
- rdoc (3.9.2)
- rspec (2.8.0)
- rspec-core (~> 2.8.0)
- rspec-expectations (~> 2.8.0)
- rspec-mocks (~> 2.8.0)
- rspec-core (2.8.0)
- rspec-expectations (2.8.0)
- diff-lcs (~> 1.1.2)
- rspec-mocks (2.8.0)
- rubyzip (0.9.6.1)
-
-PLATFORMS
- java
- ruby
-
-DEPENDENCIES
- childprocess
- diff-lcs
- jruby-jars
- jruby-openssl
- jruby-rack
- rake
- rcov (>= 0.9.8)
- rdoc
- rspec
- rubyzip
7 Rakefile
View
@@ -7,9 +7,10 @@
begin
require 'bundler/setup'
-rescue LoadError
- puts $!
- puts "Please install Bundler and run 'bundle install' to ensure you have all dependencies"
+rescue LoadError => e
+ require('rubygems') && retry
+ puts "Please `gem install bundler' and run `bundle install' to ensure you have all dependencies"
+ raise e
end
require 'bundler/gem_helper'
229 ext/JarMain.java
View
@@ -8,111 +8,164 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class JarMain implements Runnable {
- public static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
-
- private String[] args;
- private String path, jarfile;
- private boolean debug;
- private File extractRoot;
-
- public JarMain(String[] args) throws Exception {
+
+ static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
+
+ final boolean debug = isDebug();
+
+ protected final String[] args;
+ protected final String archive;
+ private final String path;
+
+ protected File extractRoot;
+
+ JarMain(String[] args) {
this.args = args;
URL mainClass = getClass().getResource(MAIN);
- this.path = mainClass.toURI().getSchemeSpecificPart();
- this.jarfile = this.path.replace("!" + MAIN, "").replace("file:", "");
- this.debug = isDebug();
- this.extractRoot = File.createTempFile("jruby", "extract");
- this.extractRoot.delete();
- this.extractRoot.mkdirs();
+ try {
+ this.path = mainClass.toURI().getSchemeSpecificPart();
+ }
+ catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ archive = this.path.replace("!" + MAIN, "").replace("file:", "");
+
Runtime.getRuntime().addShutdownHook(new Thread(this));
}
+
+ protected URL[] extractArchive() throws Exception {
+ final JarFile jarFile = new JarFile(archive);
+ try {
+ Map<String, JarEntry> jarNames = new HashMap<String, JarEntry>();
+ for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
+ JarEntry entry = e.nextElement();
+ String extractPath = getExtractEntryPath(entry);
+ if ( extractPath != null ) jarNames.put(extractPath, entry);
+ }
+
+ extractRoot = File.createTempFile("jruby", "extract");
+ extractRoot.delete(); extractRoot.mkdirs();
- private URL[] extractJRuby() throws Exception {
- JarFile jf = new JarFile(this.jarfile);
- List<String> jarNames = new ArrayList<String>();
- for (Enumeration<JarEntry> eje = jf.entries(); eje.hasMoreElements(); ) {
- String name = eje.nextElement().getName();
- if (name.startsWith("META-INF/lib") && name.endsWith(".jar")) {
- jarNames.add("/" + name);
+ final List<URL> urls = new ArrayList<URL>();
+ for (Map.Entry<String, JarEntry> e : jarNames.entrySet()) {
+ URL entryURL = extractEntry(e.getValue(), e.getKey());
+ if (entryURL != null) urls.add( entryURL );
}
+ return (URL[]) urls.toArray(new URL[urls.size()]);
}
-
- List<URL> urls = new ArrayList<URL>();
- for (String name : jarNames) {
- urls.add(extractJar(name));
+ finally {
+ jarFile.close();
}
-
- return (URL[]) urls.toArray(new URL[urls.size()]);
}
- private URL extractJar(String jarpath) throws Exception {
- InputStream jarStream = new URI("jar", path.replace(MAIN, jarpath), null).toURL().openStream();
- String jarname = jarpath.substring(jarpath.lastIndexOf("/") + 1, jarpath.lastIndexOf("."));
- File jarFile = new File(extractRoot, jarname + ".jar");
- jarFile.deleteOnExit();
- FileOutputStream outStream = new FileOutputStream(jarFile);
+ protected String getExtractEntryPath(final JarEntry entry) {
+ final String name = entry.getName();
+ if ( name.startsWith("META-INF/lib") && name.endsWith(".jar") ) {
+ return name.substring(name.lastIndexOf("/") + 1);
+ }
+ return null; // do not extract entry
+ }
+
+ protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
+ final File file = new File(extractRoot, path);
+ if ( entry.isDirectory() ) {
+ file.mkdirs();
+ return null;
+ }
+ final String entryPath = entryPath(entry.getName());
+ final InputStream entryStream;
+ try {
+ entryStream = new URI("jar", entryPath, null).toURL().openStream();
+ }
+ catch (IllegalArgumentException e) {
+ // TODO gems '%' file name "encoding" ?!
+ debug("failed to open jar:" + entryPath + " skipping entry: " + entry.getName(), e);
+ return null;
+ }
+ final File parent = file.getParentFile();
+ if ( parent != null ) parent.mkdirs();
+ FileOutputStream outStream = new FileOutputStream(file);
+ final byte[] buf = new byte[65536];
try {
- byte[] buf = new byte[65536];
int bytesRead = 0;
- while ((bytesRead = jarStream.read(buf)) != -1) {
+ while ((bytesRead = entryStream.read(buf)) != -1) {
outStream.write(buf, 0, bytesRead);
}
- } finally {
- jarStream.close();
+ }
+ finally {
+ entryStream.close();
outStream.close();
+ file.deleteOnExit();
}
- debug(jarname + ".jar extracted to " + jarFile.getPath());
- return jarFile.toURI().toURL();
+ if (false) debug(entry.getName() + " extracted to " + file.getPath());
+ return file.toURI().toURL();
+ }
+
+ protected String entryPath(String name) {
+ if ( ! name.startsWith("/") ) name = "/" + name;
+ return path.replace(MAIN, name);
}
- private int launchJRuby(URL[] jars) throws Exception {
+ protected Object newScriptingContainer(final URL[] jars) throws Exception {
System.setProperty("org.jruby.embed.class.path", "");
- URLClassLoader loader = new URLClassLoader(jars);
+ ClassLoader loader = new URLClassLoader(jars);
Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, loader);
Object scriptingContainer = scriptingContainerClass.newInstance();
-
- Method argv = scriptingContainerClass.getDeclaredMethod("setArgv", new Class[] {String[].class});
- argv.invoke(scriptingContainer, new Object[] {args});
- Method setClassLoader = scriptingContainerClass.getDeclaredMethod("setClassLoader", new Class[] {ClassLoader.class});
- setClassLoader.invoke(scriptingContainer, new Object[] {loader});
- debug("invoking " + jarfile + " with: " + Arrays.deepToString(args));
-
- Method runScriptlet = scriptingContainerClass.getDeclaredMethod("runScriptlet", new Class[] {String.class});
- return ((Number) runScriptlet.invoke(scriptingContainer, new Object[] {
- "begin\n" +
- "require 'META-INF/init.rb'\n" +
- "require 'META-INF/main.rb'\n" +
- "0\n" +
- "rescue SystemExit => e\n" +
- "e.status\n" +
- "end"
- })).intValue();
+ debug("scripting container class loader urls: " + Arrays.toString(jars));
+ invokeMethod(scriptingContainer, "setArgv", (Object) args);
+ invokeMethod(scriptingContainer, "setClassLoader", new Class[] { ClassLoader.class }, loader);
+ return scriptingContainer;
+ }
+
+ protected int launchJRuby(final URL[] jars) throws Exception {
+ final Object scriptingContainer = newScriptingContainer(jars);
+ debug("invoking " + archive + " with: " + Arrays.deepToString(args));
+ Object outcome = invokeMethod(scriptingContainer, "runScriptlet", launchScript());
+ return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
}
- private int start() throws Exception {
- URL[] u = extractJRuby();
- return launchJRuby(u);
+ protected String launchScript() {
+ return
+ "begin\n" +
+ " require 'META-INF/init.rb'\n" +
+ " require 'META-INF/main.rb'\n" +
+ " 0\n" +
+ "rescue SystemExit => e\n" +
+ " e.status\n" +
+ "end";
+ }
+
+ protected int start() throws Exception {
+ final URL[] jars = extractArchive();
+ return launchJRuby(jars);
}
- private void debug(String msg) {
- if (debug) {
- System.out.println(msg);
- }
+ protected void debug(String msg) {
+ debug(msg, null);
}
- private void delete(File f) {
+ protected void debug(String msg, Throwable t) {
+ if (debug) System.out.println(msg);
+ if (debug && t != null) t.printStackTrace(System.out);
+ }
+
+ protected void delete(File f) {
if (f.isDirectory()) {
File[] children = f.listFiles();
for (int i = 0; i < children.length; i++) {
@@ -121,29 +174,57 @@ private void delete(File f) {
}
f.delete();
}
-
+
public void run() {
- delete(extractRoot);
+ if ( extractRoot != null ) delete(extractRoot);
}
public static void main(String[] args) {
+ doStart(new JarMain(args));
+ }
+
+ protected static void doStart(final JarMain main) {
try {
- int exit = new JarMain(args).start();
- System.exit(exit);
- } catch (Exception e) {
+ System.exit( main.start() );
+ }
+ catch (Exception e) {
+ System.err.println("error: " + e.toString());
Throwable t = e;
while (t.getCause() != null && t.getCause() != t) {
t = t.getCause();
}
-
if (isDebug()) {
t.printStackTrace();
}
System.exit(1);
}
}
+
+ protected static Object invokeMethod(final Object self, final String name, final Object... args)
+ throws NoSuchMethodException, IllegalAccessException, Exception {
+
+ final Class[] signature = new Class[args.length];
+ for ( int i = 0; i < args.length; i++ ) signature[i] = args[i].getClass();
+ return invokeMethod(self, name, signature, args);
+ }
- private static boolean isDebug() {
- return System.getProperty("warbler.debug") != null;
+ protected static Object invokeMethod(final Object self, final String name, final Class[] signature, final Object... args)
+ throws NoSuchMethodException, IllegalAccessException, Exception {
+ Method method = self.getClass().getDeclaredMethod(name, signature);
+ try {
+ return method.invoke(self, args);
+ }
+ catch (InvocationTargetException e) {
+ Throwable target = e.getTargetException();
+ if (target instanceof Exception) {
+ throw (Exception) target;
+ }
+ throw e;
+ }
+ }
+
+ static boolean isDebug() {
+ return Boolean.getBoolean("warbler.debug");
}
+
}
225 ext/WarMain.java
View
@@ -5,17 +5,19 @@
* See the file LICENSE.txt for details.
*/
-import java.net.URI;
-import java.net.URLClassLoader;
-import java.net.URL;
import java.lang.reflect.Method;
-import java.io.IOException;
import java.io.InputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.net.URI;
+import java.net.URLClassLoader;
+import java.net.URL;
import java.util.Arrays;
+import java.util.List;
import java.util.Properties;
import java.util.Map;
+import java.util.jar.JarEntry;
/**
* Used as a Main-Class in the manifest for a .war file, so that you can run
@@ -53,32 +55,48 @@
* jetty.home = {{webroot}}
* </pre>
*/
-public class WarMain implements Runnable {
- public static final String MAIN = "/" + WarMain.class.getName().replace('.', '/') + ".class";
- public static final String WEBSERVER_PROPERTIES = "/WEB-INF/webserver.properties";
- public static final String WEBSERVER_JAR = "/WEB-INF/webserver.jar";
-
- private String[] args;
- private String path, warfile;
- private boolean debug;
+public class WarMain extends JarMain {
+
+ static final String MAIN = "/" + WarMain.class.getName().replace('.', '/') + ".class";
+ static final String WEBSERVER_PROPERTIES = "/WEB-INF/webserver.properties";
+ static final String WEBSERVER_JAR = "/WEB-INF/webserver.jar";
+
+ // jruby arguments, consider the following command :
+ // `java -jar rails.was --1.9 -S rake db:migrate`
+ // arguments == [ "--1.9" ]
+ // executable == "rake"
+ // executableArgv == [ "db:migrate" ]
+ private final String[] arguments;
+ // null to launch webserver or != null to run a executable e.g. rake
+ private final String executable;
+ private final String[] executableArgv;
+
private File webroot;
- public WarMain(String[] args) throws Exception {
- this.args = args;
- URL mainClass = getClass().getResource(MAIN);
- this.path = mainClass.toURI().getSchemeSpecificPart();
- this.warfile = this.path.replace("!" + MAIN, "").replace("file:", "");
- this.debug = isDebug();
+ WarMain(final String[] args) {
+ super(args);
+ final List<String> argsList = Arrays.asList(args);
+ final int sIndex = argsList.indexOf("-S");
+ if ( sIndex == -1 ) {
+ executable = null; executableArgv = null; arguments = null;
+ }
+ else {
+ if ( args.length == sIndex + 1 || args[sIndex + 1].isEmpty() ) {
+ throw new IllegalArgumentException("missing executable after -S");
+ }
+ arguments = argsList.subList(0, sIndex).toArray(new String[0]);
+ executable = argsList.get(sIndex + 1);
+ executableArgv = argsList.subList(sIndex + 2, argsList.size()).toArray(new String[0]);
+ }
+ }
+
+ private URL extractWebserver() throws Exception {
this.webroot = File.createTempFile("warbler", "webroot");
this.webroot.delete();
this.webroot.mkdirs();
- this.webroot = new File(this.webroot, new File(warfile).getName());
+ this.webroot = new File(this.webroot, new File(archive).getName());
debug("webroot directory is " + this.webroot.getPath());
- Runtime.getRuntime().addShutdownHook(new Thread(this));
- }
-
- private URL extractWebserver() throws Exception {
- InputStream jarStream = new URI("jar", path.replace(MAIN, WEBSERVER_JAR), null).toURL().openStream();
+ InputStream jarStream = new URI("jar", entryPath(WEBSERVER_JAR), null).toURL().openStream();
File jarFile = File.createTempFile("webserver", ".jar");
jarFile.deleteOnExit();
FileOutputStream outStream = new FileOutputStream(jarFile);
@@ -100,13 +118,12 @@ private Properties getWebserverProperties() throws Exception {
Properties props = new Properties();
try {
InputStream is = getClass().getResourceAsStream(WEBSERVER_PROPERTIES);
- props.load(is);
- } catch (Exception e) {
- }
+ if ( is != null ) props.load(is);
+ } catch (Exception e) { }
for (Map.Entry entry : props.entrySet()) {
String val = (String) entry.getValue();
- val = val.replace("{{warfile}}", warfile).replace("{{webroot}}", webroot.getAbsolutePath());
+ val = val.replace("{{warfile}}", archive).replace("{{webroot}}", webroot.getAbsolutePath());
entry.setValue(val);
}
@@ -120,7 +137,7 @@ private Properties getWebserverProperties() throws Exception {
return props;
}
- private void launchWebserver(URL jar) throws Exception {
+ private void launchWebServer(URL jar) throws Exception {
URLClassLoader loader = new URLClassLoader(new URL[] {jar});
Thread.currentThread().setContextClassLoader(loader);
Properties props = getWebserverProperties();
@@ -131,66 +148,142 @@ private void launchWebserver(URL jar) throws Exception {
+ " is missing 'mainclass' property)");
}
Class klass = Class.forName(mainClass, true, loader);
- Method main = klass.getDeclaredMethod("main", new Class[] {String[].class});
- String[] newargs = launchArguments(props);
- debug("invoking webserver with: " + Arrays.deepToString(newargs));
- main.invoke(null, new Object[] {newargs});
+ Method main = klass.getDeclaredMethod("main", new Class[] { String[].class });
+ String[] newArgs = launchWebServerArguments(props);
+ debug("invoking webserver with: " + Arrays.deepToString(newArgs));
+ main.invoke(null, new Object[] { newArgs });
}
- private String[] launchArguments(Properties props) {
- String[] newargs = args;
+ private String[] launchWebServerArguments(Properties props) {
+ String[] newArgs = args;
if (props.getProperty("args") != null) {
String[] insertArgs = props.getProperty("args").split(",");
- newargs = new String[args.length + insertArgs.length];
+ newArgs = new String[args.length + insertArgs.length];
for (int i = 0; i < insertArgs.length; i++) {
- newargs[i] = props.getProperty(insertArgs[i], "");
+ newArgs[i] = props.getProperty(insertArgs[i], "");
}
- System.arraycopy(args, 0, newargs, insertArgs.length, args.length);
+ System.arraycopy(args, 0, newArgs, insertArgs.length, args.length);
}
- return newargs;
+ return newArgs;
}
- private void start() throws Exception {
- URL u = extractWebserver();
- launchWebserver(u);
+ // JarMain overrides to make WarMain "launchable"
+ // e.g. java -jar rails.war -S rake db:migrate
+
+ @Override
+ protected String getExtractEntryPath(final JarEntry entry) {
+ final String name = entry.getName();
+ final String start = "WEB-INF";
+ if ( name.startsWith(start) ) {
+ // WEB-INF/app/controllers/application_controller.rb ->
+ // app/controllers/application_controller.rb
+ return name.substring(start.length());
+ }
+ if ( name.indexOf('/') == -1 ) {
+ // 404.html -> public/404.html
+ return "/public/" + name;
+ }
+ return "/" + name;
}
-
- private void debug(String msg) {
- if (debug) {
- System.out.println(msg);
+
+ @Override
+ protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
+ // always extract but only return class-path entry URLs :
+ final URL entryURL = super.extractEntry(entry, path);
+ return path.endsWith(".jar") ? entryURL : null;
+ }
+
+ @Override
+ protected int launchJRuby(final URL[] jars) throws Exception {
+ final Object scriptingContainer = newScriptingContainer(jars);
+
+ invokeMethod(scriptingContainer, "setArgv", (Object) executableArgv);
+ //invokeMethod(scriptingContainer, "setHomeDirectory", "classpath:/META-INF/jruby.home");
+ invokeMethod(scriptingContainer, "setCurrentDirectory", extractRoot.getAbsolutePath());
+ //invokeMethod(scriptingContainer, "runScriptlet", "ENV.clear");
+ //invokeMethod(scriptingContainer, "runScriptlet", "ENV['PATH']=''"); // bundler 1.1.x
+
+ final Object provider = invokeMethod(scriptingContainer, "getProvider");
+ final Object rubyInstanceConfig = invokeMethod(provider, "getRubyInstanceConfig");
+
+ invokeMethod(rubyInstanceConfig, "setUpdateNativeENVEnabled", new Class[] { Boolean.TYPE }, false);
+
+ final String executablePath = (String)
+ invokeMethod(scriptingContainer, "runScriptlet", locateExecutableScript());
+ if ( executablePath == null ) {
+ throw new IllegalStateException("failed to locate gem executable: '" + executable + "'");
}
+ invokeMethod(scriptingContainer, "setScriptFilename", executablePath);
+
+ invokeMethod(rubyInstanceConfig, "processArguments", (Object) arguments);
+
+ Object executableInput = invokeMethod(rubyInstanceConfig, "getScriptSource");
+ Object runtime = invokeMethod(scriptingContainer, "getRuntime");
+
+ debug("invoking " + executablePath + " with: " + Arrays.toString(executableArgv));
+ Object outcome = invokeMethod(runtime, "runFromMain",
+ new Class[] { InputStream.class, String.class },
+ executableInput, executablePath
+ );
+ return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
}
-
- private void delete(File f) {
- if (f.isDirectory()) {
- File[] children = f.listFiles();
- for (int i = 0; i < children.length; i++) {
- delete(children[i]);
+
+ protected String locateExecutableScript() {
+ if ( executable == null ) {
+ throw new IllegalStateException("no exexutable");
+ }
+ final String gemsDir = new File(extractRoot, "gems").getAbsolutePath();
+ final String gemfile = new File(extractRoot, "Gemfile").getAbsolutePath();
+ debug("setting GEM_HOME to " + gemsDir);
+ debug("... and BUNDLE_GEMFILE to " + gemfile);
+ return
+ "ENV['GEM_HOME'] = ENV['GEM_PATH'] = '"+ gemsDir +"' \n" +
+ "ENV['BUNDLE_GEMFILE'] = '"+ gemfile +"' \n" +
+ "begin\n" +
+ " require 'META-INF/init.rb' \n" +
+ // locate the executable within gemspecs :
+ " require 'rubygems' \n" +
+ " exec = '"+ executable +"' \n" +
+ " spec = Gem::Specification.find { |s| s.executables.include?(exec) } \n" +
+ " spec ? spec.bin_file(exec) : nil \n" +
+ // returns the full path to the executable
+ "rescue SystemExit => e\n" +
+ " e.status\n" +
+ "end";
+ }
+
+ @Override
+ protected int start() throws Exception {
+ if ( executable == null ) {
+ try {
+ URL server = extractWebserver();
+ launchWebServer(server);
+ }
+ catch (FileNotFoundException e) {
+ if ( e.getMessage().indexOf("WEB-INF/webserver.jar") > -1 ) {
+ System.out.println("specify the -S argument followed by the bin file to run e.g. `java -jar rails.war -S rake -T` ...");
+ System.out.println("(or if you'd like your .war file to start a web server package it using `warbler executable war`)");
+ }
+ throw e;
}
+ return 0;
+ }
+ else {
+ return super.start();
}
- f.delete();
}
+ @Override
public void run() {
- delete(webroot.getParentFile());
+ super.run();
+ if ( webroot != null ) delete(webroot.getParentFile());
}
public static void main(String[] args) {
- try {
- new WarMain(args).start();
- } catch (Exception e) {
- System.err.println("error: " + e.toString());
- if (isDebug()) {
- e.printStackTrace();
- }
- System.exit(1);
- }
+ doStart(new WarMain(args));
}
- private static boolean isDebug() {
- return System.getProperty("warbler.debug") != null;
- }
}
3  lib/warbler.rb
View
@@ -9,7 +9,8 @@
# your Ruby applications into .jar or .war files.
module Warbler
WARBLER_HOME = File.expand_path(File.dirname(__FILE__) + '/..') unless defined?(WARBLER_HOME)
-
+ WARBLER_JAR = "#{WARBLER_HOME}/lib/warbler_jar.jar" unless defined?(WARBLER_JAR)
+
class << self
# An instance of Warbler::Application used by the +warble+ command.
attr_accessor :application
5 lib/warbler/application.rb
View
@@ -37,9 +37,12 @@ def load_rakefile
desc "Feature: package gem repository inside a jar"
task :gemjar => "#{wt.name}:gemjar"
- desc "Feature: make an executable archive"
+ desc "Feature: make an executable archive (runnable + an embedded web server)"
task :executable => "#{wt.name}:executable"
+ desc "Feature: make a runnable archive (e.g. java -jar rails.war -S rake db:migrate)"
+ task :runnable => "#{wt.name}:runnable"
+
desc "Feature: precompile all Ruby files"
task :compiled => "#{wt.name}:compiled"
8 lib/warbler/config.rb
View
@@ -14,7 +14,7 @@ module Warbler
class Config
include RakeHelper
- TOP_DIRS = %w(app config lib log vendor)
+ TOP_DIRS = %w(app db config lib log script vendor)
FILE = "config/warble.rb"
BUILD_GEMS = %w(warbler rake rcov)
@@ -158,7 +158,7 @@ def initialize(warbler_home = WARBLER_HOME)
@warbler_templates = "#{WARBLER_HOME}/lib/warbler/templates"
@features = Set.new
@dirs = TOP_DIRS.select {|d| File.directory?(d)}
- @includes = FileList[]
+ @includes = FileList['*file'] # [r/R]akefile gets included
@excludes = FileList[]
@java_libs = FileList[]
@java_classes = FileList[]
@@ -180,6 +180,7 @@ def initialize(warbler_home = WARBLER_HOME)
@compiled_ruby_files ||= FileList[*@dirs.map {|d| "#{d}/**/*.rb"}]
@excludes += ["tmp/war", "tmp/war/**/*"] if File.directory?("tmp/war")
+ @excludes += ["tmp/cache/**/*"] if File.directory?("tmp/cache")
@excludes += warbler_vendor_excludes(warbler_home)
@excludes += FileList["**/*.log"] if @exclude_logs
end
@@ -199,6 +200,9 @@ def define_tasks
task "executable" do
self.features << "executable"
end
+ task "runnable" do
+ self.features << "runnable"
+ end
end
# Deprecated
4 lib/warbler/templates/war.erb
View
@@ -1,6 +1,4 @@
ENV['GEM_HOME'] ||= $servlet_context.getRealPath('<%= config.gem_path %>')
<% if config.bundler && config.bundler[:gemfile_path] %>
-ENV['BUNDLE_GEMFILE'] = $servlet_context.getRealPath('/<%= config.bundler[:gemfile_path] %>')
+ENV['BUNDLE_GEMFILE'] ||= $servlet_context.getRealPath('/<%= config.bundler[:gemfile_path] %>')
<% end %>
-
-
7 lib/warbler/traits/jar.rb
View
@@ -33,8 +33,11 @@ def after_configure
end
def update_archive(jar)
- jar.files['META-INF/MANIFEST.MF'] = StringIO.new(Warbler::Jar::DEFAULT_MANIFEST.chomp + "Main-Class: JarMain\n") unless config.manifest_file
- jar.files['JarMain.class'] = jar.entry_in_jar("#{WARBLER_HOME}/lib/warbler_jar.jar", "JarMain.class")
+ unless config.manifest_file
+ manifest = Warbler::Jar::DEFAULT_MANIFEST.chomp + "Main-Class: JarMain\n"
+ jar.files['META-INF/MANIFEST.MF'] = StringIO.new(manifest)
+ end
+ jar.files['JarMain.class'] = jar.entry_in_jar(WARBLER_JAR, "JarMain.class")
end
def default_pathmaps
15 lib/warbler/traits/war.rb
View
@@ -77,6 +77,7 @@ def update_archive(jar)
add_public_files(jar)
add_webxml(jar)
move_jars_to_webinf_lib(jar)
+ add_runnables(jar) if config.features.include?("runnable")
add_executables(jar) if config.features.include?("executable")
add_gemjar(jar) if config.features.include?("gemjar")
end
@@ -99,11 +100,21 @@ def add_webxml(jar)
end
end
+ def add_runnables(jar, main_class = 'WarMain')
+ main_class = main_class.sub('.class', '') # handles WarMain.class
+ unless config.manifest_file
+ manifest = Warbler::Jar::DEFAULT_MANIFEST.chomp + "Main-Class: #{main_class}\n"
+ jar.files['META-INF/MANIFEST.MF'] = StringIO.new(manifest)
+ end
+ [ 'JarMain', 'WarMain', main_class ].uniq.each do |klass|
+ jar.files["#{klass}.class"] = jar.entry_in_jar(WARBLER_JAR, "#{klass}.class")
+ end
+ end
+
def add_executables(jar)
webserver = WEB_SERVERS[config.webserver.to_s]
webserver.add(jar)
- jar.files['META-INF/MANIFEST.MF'] = StringIO.new(Warbler::Jar::DEFAULT_MANIFEST.chomp + "Main-Class: WarMain\n")
- jar.files['WarMain.class'] = jar.entry_in_jar("#{WARBLER_HOME}/lib/warbler_jar.jar", webserver.main_class)
+ add_runnables jar, webserver.main_class || 'WarMain'
end
def add_gemjar(jar)
BIN  lib/warbler_jar.jar
View
Binary file not shown
6 spec/sample_war/Rakefile
View
@@ -6,6 +6,10 @@
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+begin
+ require 'rdoc/task'
+rescue LoadError
+ require 'rake/rdoctask'
+end
#require 'tasks/rails'
2  spec/spec_helper.rb
View
@@ -15,7 +15,7 @@
Warbler specs are destructive to application directories.} if File.directory?("app")
require 'rbconfig'
-RUBY_EXE = File.join Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']
+RUBY_EXE = File.join RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']
require 'fileutils'
require 'stringio'
2  spec/warbler/bundler_spec.rb
View
@@ -102,7 +102,7 @@ def use_config(&block)
it "adds BUNDLE_GEMFILE to init.rb" do
jar.add_init_file(config)
contents = jar.contents('META-INF/init.rb')
- contents.should =~ Regexp.new(Regexp.quote("ENV['BUNDLE_GEMFILE'] = $servlet_context.getRealPath('/WEB-INF/Gemfile')"))
+ contents.should =~ Regexp.new(Regexp.quote("ENV['BUNDLE_GEMFILE'] ||= $servlet_context.getRealPath('/WEB-INF/Gemfile')"))
end
it "uses ENV['BUNDLE_GEMFILE'] if set" do
2  spec/warbler/config_spec.rb
View
@@ -32,7 +32,7 @@
it "should have suitable default values" do
config = Warbler::Config.new
config.dirs.should include(*Warbler::Config::TOP_DIRS.select{|d| File.directory?(d)})
- config.includes.should be_empty
+ config.excludes.should be_empty
config.java_libs.should_not be_empty
config.jar_name.size.should > 0
config.webxml.should be_kind_of(OpenStruct)
16 spec/warbler/jar_spec.rb
View
@@ -475,16 +475,30 @@ class << t; public :instance_variable_get; end
context "with the executable feature" do
use_test_webserver
- it "adds a WarMain class" do
+ it "adds WarMain (and JarMain) class" do
use_config do |config|
config.webserver = "test"
config.features << "executable"
end
jar.apply(config)
file_list(%r{^WarMain\.class$}).should_not be_empty
+ file_list(%r{^JarMain\.class$}).should_not be_empty
end
end
+ context "with the runnable feature" do
+
+ it "adds WarMain (and JarMain) class" do
+ use_config do |config|
+ config.features << "runnable"
+ end
+ jar.apply(config)
+ file_list(%r{^WarMain\.class$}).should_not be_empty
+ file_list(%r{^JarMain\.class$}).should_not be_empty
+ end
+
+ end
+
context "in a Rails application" do
before :each do
@rails = nil
2  warble.rb
View
@@ -11,7 +11,7 @@
# config.features = %w(gemjar)
# Application directories to be included in the webapp.
- config.dirs = %w(app config lib log vendor tmp)
+ config.dirs = %w(app config db lib log vendor tmp)
# Additional files/directories to include, above those in config.dirs
# config.includes = FileList["db"]
42 warbler.gemspec
View
@@ -1,31 +1,31 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require 'warbler/version'
-version = Warbler::VERSION
-Gem::Specification.new do |s|
- s.name = "warbler"
- s.version = version
- s.platform = Gem::Platform::RUBY
- s.homepage = "http://caldersphere.rubyforge.org/warbler"
- s.authors = ["Nick Sieger"]
- s.email = "nick@nicksieger.com"
- s.summary = "Warbler chirpily constructs .war files of your Rails applications."
- s.description = %q{Warbler is a gem to make a Java jar or war file out of any Ruby,
+
+Gem::Specification.new do |gem|
+ gem.name = "warbler"
+ gem.version = Warbler::VERSION
+ gem.platform = Gem::Platform::RUBY
+ gem.homepage = "http://caldersphere.rubyforge.org/warbler"
+ gem.authors = ["Nick Sieger"]
+ gem.email = "nick@nicksieger.com"
+ gem.summary = "Warbler chirpily constructs .war files of your Rails applications."
+ gem.description = %q{Warbler is a gem to make a Java jar or war file out of any Ruby,
Rails, Merb, or Rack application. Warbler provides a minimal,
flexible, Ruby-like way to bundle up all of your application files for
deployment to a Java environment.}
+ gem.files = `git ls-files`.split("\n")
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ gem.require_paths = ["lib"]
- s.files = `git ls-files`.split("\n")
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
- s.require_paths = ["lib"]
-
- s.rdoc_options = ["--main", "README.rdoc", "-H", "-f", "darkfish"]
- s.rubyforge_project = "caldersphere"
+ gem.rdoc_options = ["--main", "README.rdoc", "-H", "-f", "darkfish"]
+ gem.rubyforge_project = "caldersphere"
- s.add_runtime_dependency(%q<rake>, [">= 0.8.7"])
- s.add_runtime_dependency(%q<jruby-jars>, [">= 1.4.0"])
- s.add_runtime_dependency(%q<jruby-rack>, [">= 1.0.0"])
- s.add_runtime_dependency(%q<rubyzip>, [">= 0.9.4"])
+ gem.add_runtime_dependency 'rake', [">= 0.9.6"]
+ gem.add_runtime_dependency 'jruby-jars', [">= 1.5.6"]
+ gem.add_runtime_dependency 'jruby-rack', [">= 1.0.0"]
+ gem.add_runtime_dependency 'rubyzip', [">= 0.9.8"]
+ gem.add_development_dependency 'rspec', "~> 2.10"
end
Something went wrong with that request. Please try again.