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 #136

Merged
merged 14 commits into from

4 participants

@BanzaiMan
Owner

This pull requests supersedes #130, which was created by @kares.

@BanzaiMan
Owner

The gem is available here: http://db.tt/R1okA4Cm (a Dropbox link).

@kares
Collaborator

@BanzaiMan great, looking good :shipit: - let me know if there's anything I can help / explain ...
although most of the code is almost a year old now and (has been laying around my local branch)

@BanzaiMan
Owner

@kares Good to hear. Can we have documentation (README is fine, since we don't have Wiki here)?

@kares kares referenced this pull request from a commit in kares/warbler
@kares kares README review , most importantly :
* added notes on executable/runnable feature support (#136)
* removed notes on Merb support (JRuby-Rack will remove Merb support completely + it's been most likely non-working in 1.1.x releases anyway)
* expect "compiled" .rb to be changed to do a load instead of require - this has been a long incompatibility issue and is present in #134
c1c6950
@kares kares referenced this pull request
Merged

README review #138

@BanzaiMan BanzaiMan merged commit ce3ce4d into from
@bjeanes

Is this supposed to work for non-rake commands? I couldn't get any other gem commands to work, even after bundling with --binstubs and adding bin to the Warbler config's dirs array.

From a cursory glance at the diff, it doesn't seem to be rake-specific, but I can't make sense of how it works either, so looking for some direction.

@bjeanes bjeanes referenced this pull request in SquareSquash/web
Closed

Support building a deployable and self-executable WAR file of Squash #57

5 of 6 tasks complete
@bjeanes

I'm now working on a pull request (#141) to improve this functionality and the implementing code in order to make my use case work (successfully running Bundler gem binaries).

@nicksieger Are there tests for the likes of WarMain and JarMain, etc?

@bbozo

Thank you, beautiful people, using @bjeanes branch atm, saved lives

:+1:

@bjeanes

Awesome! I'm glad my branch is helping you out. If we can get more eyes on #141 maybe it can help others too :)

@richievos richievos referenced this pull request
Open

git sourced gems #235

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 28, 2012
  1. @kares
  2. @kares
  3. @kares
  4. @kares
  5. @kares
  6. @kares
  7. @kares
  8. @kares
  9. @kares

    package db/ dir + include *file-s (e.g. Rakefile) by default

    kares authored
    exclude tmp/cache since it might contain (compiled) assets
  10. @kares
  11. @kares
  12. @kares

    include a runnable task (executable wihout the web server)

    kares authored
    this allows to make a war for running packagted binaries such as `rake db:migrate` while not embedding a web server
  13. @kares
Commits on Jan 31, 2013
  1. @BanzaiMan

    Merge remote-tracking branch 'kares/runnable' into gh-130

    BanzaiMan authored
    Conflicts:
    	.gitignore
    	Gemfile
    	ext/JarMain.java
    	spec/sample_war/Rakefile
This page is out of date. Refresh to see the latest.
View
5 Gemfile
@@ -1,9 +1,6 @@
source "http://rubygems.org/"
-gem "rake"
-gem "rubyzip"
-gem "jruby-jars"
-gem "jruby-rack"
+gemspec
group :development do
gem "jruby-openssl", :platform => :jruby
View
7 Rakefile
@@ -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'
View
223 ext/JarMain.java
@@ -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,30 +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();
if(isSystemExitEnabled()) System.exit(exit);
} 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");
}
/**
@@ -155,4 +235,5 @@ private static boolean isDebug() {
private static boolean isSystemExitEnabled(){
return System.getProperty("warbler.skip_system_exit") == null; //omission enables System.exit use
}
+
}
View
225 ext/WarMain.java
@@ -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;
- }
}
View
3  lib/warbler.rb
@@ -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
View
5 lib/warbler/application.rb
@@ -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"
View
8 lib/warbler/config.rb
@@ -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
View
4 lib/warbler/templates/war.erb
@@ -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 %>
-
-
View
7 lib/warbler/traits/jar.rb
@@ -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
View
15 lib/warbler/traits/war.rb
@@ -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)
View
BIN  lib/warbler_jar.jar
Binary file not shown
View
6 spec/sample_war/Rakefile
@@ -6,6 +6,10 @@
require 'rake'
require 'rake/testtask'
-require 'rdoc/task'
+begin
+ require 'rdoc/task'
+rescue LoadError
+ require 'rake/rdoctask'
+end
#require 'tasks/rails'
View
2  spec/warbler/bundler_spec.rb
@@ -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
View
2  spec/warbler/config_spec.rb
@@ -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)
View
16 spec/warbler/jar_spec.rb
@@ -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
View
2  warble.rb
@@ -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"]
View
42 warbler.gemspec
@@ -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.