warbler_updated 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/History.txt +411 -0
- data/LICENSE.txt +27 -0
- data/Mavenfile +32 -0
- data/README.rdoc +280 -0
- data/Rakefile +63 -0
- data/bin/warble +11 -0
- data/ext/JarMain.java +334 -0
- data/ext/WarMain.java +375 -0
- data/ext/WarblerJar.java +199 -0
- data/ext/WarblerJarService.java +18 -0
- data/lib/warbler/application.rb +104 -0
- data/lib/warbler/bundler_helper.rb +22 -0
- data/lib/warbler/config.rb +265 -0
- data/lib/warbler/executable_helper.rb +25 -0
- data/lib/warbler/gems.rb +77 -0
- data/lib/warbler/jar.rb +348 -0
- data/lib/warbler/pathmap_helper.rb +20 -0
- data/lib/warbler/platform_helper.rb +26 -0
- data/lib/warbler/rake_helper.rb +30 -0
- data/lib/warbler/scripts/rails.rb +5 -0
- data/lib/warbler/task.rb +185 -0
- data/lib/warbler/templates/bundler.erb +19 -0
- data/lib/warbler/templates/config.erb +1 -0
- data/lib/warbler/templates/jar.erb +11 -0
- data/lib/warbler/templates/jbundler.erb +2 -0
- data/lib/warbler/templates/rack.erb +5 -0
- data/lib/warbler/templates/rails.erb +1 -0
- data/lib/warbler/templates/war.erb +19 -0
- data/lib/warbler/traits/bundler.rb +157 -0
- data/lib/warbler/traits/gemspec.rb +79 -0
- data/lib/warbler/traits/jar.rb +58 -0
- data/lib/warbler/traits/jbundler.rb +48 -0
- data/lib/warbler/traits/nogemspec.rb +47 -0
- data/lib/warbler/traits/rack.rb +33 -0
- data/lib/warbler/traits/rails.rb +91 -0
- data/lib/warbler/traits/war.rb +260 -0
- data/lib/warbler/traits.rb +124 -0
- data/lib/warbler/version.rb +10 -0
- data/lib/warbler/war.rb +8 -0
- data/lib/warbler/web_server.rb +125 -0
- data/lib/warbler/zip_support.rb +13 -0
- data/lib/warbler.rb +40 -0
- data/lib/warbler_jar.jar +0 -0
- data/warble.rb +188 -0
- data/warbler.gemspec +37 -0
- data/web.xml.erb +57 -0
- metadata +188 -0
data/ext/WarMain.java
ADDED
@@ -0,0 +1,375 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2010-2012 Engine Yard, Inc.
|
3
|
+
* Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
4
|
+
* This source code is available under the MIT license.
|
5
|
+
* See the file LICENSE.txt for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import java.lang.reflect.Method;
|
9
|
+
import java.io.InputStream;
|
10
|
+
import java.io.ByteArrayInputStream;
|
11
|
+
import java.io.SequenceInputStream;
|
12
|
+
import java.io.File;
|
13
|
+
import java.io.FileNotFoundException;
|
14
|
+
import java.io.FileOutputStream;
|
15
|
+
import java.net.URI;
|
16
|
+
import java.net.URLClassLoader;
|
17
|
+
import java.net.URL;
|
18
|
+
import java.util.Arrays;
|
19
|
+
import java.util.List;
|
20
|
+
import java.util.Properties;
|
21
|
+
import java.util.Map;
|
22
|
+
import java.util.jar.JarEntry;
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Used as a Main-Class in the manifest for a .war file, so that you can run
|
26
|
+
* a .war file with <tt>java -jar</tt>.
|
27
|
+
*
|
28
|
+
* WarMain can be used with different web server libraries. WarMain expects
|
29
|
+
* to have two files present in the .war file,
|
30
|
+
* <tt>WEB-INF/webserver.properties</tt> and <tt>WEB-INF/webserver.jar</tt>.
|
31
|
+
*
|
32
|
+
* When WarMain starts up, it extracts the webserver jar to a temporary
|
33
|
+
* directory, and creates a temporary work directory for the webapp. Both
|
34
|
+
* are deleted on exit.
|
35
|
+
*
|
36
|
+
* It then reads webserver.properties into a java.util.Properties object,
|
37
|
+
* creates a URL classloader holding the jar, and loads and invokes the
|
38
|
+
* <tt>main</tt> method of the main class mentioned in the properties.
|
39
|
+
*
|
40
|
+
* An example webserver.properties follows for Jetty. The <tt>args</tt>
|
41
|
+
* property indicates the names and ordering of other properties to be used
|
42
|
+
* as command-line arguments. The special tokens <tt>{{warfile}}</tt> and
|
43
|
+
* <tt>{{webroot}}</tt> are substituted with the location of the .war file
|
44
|
+
* being run and the temporary work directory, respectively.
|
45
|
+
* <pre>
|
46
|
+
* mainclass = org.eclipse.jetty.runner.Runner
|
47
|
+
* args = args0,args1,args2,args3,args4
|
48
|
+
* props = jetty.home
|
49
|
+
* args0 = --port
|
50
|
+
* args1 = {{port}}
|
51
|
+
* args2 = --config
|
52
|
+
* args3 = {{config}}
|
53
|
+
* args4 = {{warfile}}
|
54
|
+
* jetty.home = {{webroot}}
|
55
|
+
* </pre>
|
56
|
+
*
|
57
|
+
* System properties can also be set via webserver.properties. For example,
|
58
|
+
* the following entries set <tt>jetty.home</tt> before launching the server.
|
59
|
+
* <pre>
|
60
|
+
* props = jetty.home
|
61
|
+
* jetty.home = {{webroot}}
|
62
|
+
* </pre>
|
63
|
+
*/
|
64
|
+
public class WarMain extends JarMain {
|
65
|
+
|
66
|
+
static final String MAIN = '/' + WarMain.class.getName().replace('.', '/') + ".class";
|
67
|
+
static final String WEBSERVER_PROPERTIES = "/WEB-INF/webserver.properties";
|
68
|
+
static final String WEBSERVER_JAR = "/WEB-INF/webserver.jar";
|
69
|
+
static final String WEBSERVER_CONFIG = "/WEB-INF/webserver.xml";
|
70
|
+
static final String WEB_INF = "WEB-INF";
|
71
|
+
static final String META_INF = "META-INF";
|
72
|
+
|
73
|
+
/**
|
74
|
+
* jruby arguments, consider the following command :
|
75
|
+
* `java -jar rails.war --1.9 -S rake db:migrate`
|
76
|
+
* arguments == [ "--1.9" ]
|
77
|
+
* executable == "rake"
|
78
|
+
* executableArgv == [ "db:migrate" ]
|
79
|
+
*/
|
80
|
+
private final String[] arguments;
|
81
|
+
|
82
|
+
/**
|
83
|
+
* null to launch webserver or != null to run a executable e.g. rake
|
84
|
+
*/
|
85
|
+
private final String executable;
|
86
|
+
private final String[] executableArgv;
|
87
|
+
|
88
|
+
private File webroot;
|
89
|
+
|
90
|
+
WarMain(final String[] args) {
|
91
|
+
super(args);
|
92
|
+
final List<String> argsList = Arrays.asList(args);
|
93
|
+
final int sIndex = argsList.indexOf("-S");
|
94
|
+
if ( sIndex == -1 ) {
|
95
|
+
executable = null; executableArgv = null; arguments = null;
|
96
|
+
}
|
97
|
+
else {
|
98
|
+
if ( args.length == sIndex + 1 || args[sIndex + 1].isEmpty() ) {
|
99
|
+
throw new IllegalArgumentException("missing executable after -S");
|
100
|
+
}
|
101
|
+
arguments = argsList.subList(0, sIndex).toArray(new String[0]);
|
102
|
+
String execArg = argsList.get(sIndex + 1);
|
103
|
+
executableArgv = argsList.subList(sIndex + 2, argsList.size()).toArray(new String[0]);
|
104
|
+
|
105
|
+
if (execArg.equals("rails")) {
|
106
|
+
// The rails executable doesn't play well with ScriptingContainer, so we've packaged the
|
107
|
+
// same script that would have been generated by `rake rails:update:bin`
|
108
|
+
execArg = "./META-INF/rails.rb";
|
109
|
+
}
|
110
|
+
else if (execArg.equals("bundle") && executableArgv.length > 0 && executableArgv[0].equals("exec")) {
|
111
|
+
warn("`bundle exec' may drop out of the Warbler environment and into the system environment");
|
112
|
+
}
|
113
|
+
|
114
|
+
executable = execArg;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
private URL extractWebserver() throws Exception {
|
119
|
+
this.webroot = File.createTempFile("warbler", "webroot");
|
120
|
+
this.webroot.delete();
|
121
|
+
this.webroot.mkdirs();
|
122
|
+
this.webroot = new File(this.webroot, new File(archive).getName());
|
123
|
+
debug("webroot directory is " + this.webroot.getPath());
|
124
|
+
InputStream jarStream = new URI("jar", entryPath(WEBSERVER_JAR), null).toURL().openStream();
|
125
|
+
File jarFile = File.createTempFile("webserver", ".jar");
|
126
|
+
jarFile.deleteOnExit();
|
127
|
+
FileOutputStream outStream = new FileOutputStream(jarFile);
|
128
|
+
try {
|
129
|
+
byte[] buf = new byte[4096];
|
130
|
+
int bytesRead;
|
131
|
+
while ((bytesRead = jarStream.read(buf)) != -1) {
|
132
|
+
outStream.write(buf, 0, bytesRead);
|
133
|
+
}
|
134
|
+
} finally {
|
135
|
+
jarStream.close();
|
136
|
+
outStream.close();
|
137
|
+
}
|
138
|
+
debug("webserver.jar extracted to " + jarFile.getPath());
|
139
|
+
return jarFile.toURI().toURL();
|
140
|
+
}
|
141
|
+
|
142
|
+
private Properties getWebserverProperties() throws Exception {
|
143
|
+
Properties props = new Properties();
|
144
|
+
try {
|
145
|
+
InputStream is = getClass().getResourceAsStream(WEBSERVER_PROPERTIES);
|
146
|
+
if ( is != null ) props.load(is);
|
147
|
+
} catch (Exception e) { }
|
148
|
+
|
149
|
+
String port = getSystemProperty("warbler.port", getENV("PORT"));
|
150
|
+
port = port == null ? "8080" : port;
|
151
|
+
String host = getSystemProperty("warbler.host", "0.0.0.0");
|
152
|
+
String webserverConfig = getSystemProperty("warbler.webserver_config", getENV("WARBLER_WEBSERVER_CONFIG"));
|
153
|
+
String embeddedWebserverConfig = new URI("jar", entryPath(WEBSERVER_CONFIG), null).toURL().toString();
|
154
|
+
webserverConfig = webserverConfig == null ? embeddedWebserverConfig : webserverConfig;
|
155
|
+
for ( Map.Entry entry : props.entrySet() ) {
|
156
|
+
String val = (String) entry.getValue();
|
157
|
+
val = val.replace("{{warfile}}", archive).
|
158
|
+
replace("{{port}}", port).
|
159
|
+
replace("{{host}}", host).
|
160
|
+
replace("{{config}}", webserverConfig).
|
161
|
+
replace("{{webroot}}", webroot.getAbsolutePath());
|
162
|
+
entry.setValue(val);
|
163
|
+
}
|
164
|
+
|
165
|
+
if (props.getProperty("props") != null) {
|
166
|
+
String[] propsToSet = props.getProperty("props").split(",");
|
167
|
+
for ( String key : propsToSet ) {
|
168
|
+
setSystemProperty(key, props.getProperty(key));
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
return props;
|
173
|
+
}
|
174
|
+
|
175
|
+
private void launchWebServer(URL jar) throws Exception {
|
176
|
+
URLClassLoader loader = new URLClassLoader(new URL[] {jar});
|
177
|
+
Thread.currentThread().setContextClassLoader(loader);
|
178
|
+
Properties props = getWebserverProperties();
|
179
|
+
String mainClass = props.getProperty("mainclass");
|
180
|
+
if (mainClass == null) {
|
181
|
+
throw new IllegalArgumentException("unknown webserver main class ("
|
182
|
+
+ WEBSERVER_PROPERTIES
|
183
|
+
+ " is missing 'mainclass' property)");
|
184
|
+
}
|
185
|
+
Class<?> klass = Class.forName(mainClass, true, loader);
|
186
|
+
Method main = klass.getDeclaredMethod("main", new Class[] { String[].class });
|
187
|
+
String[] newArgs = launchWebServerArguments(props);
|
188
|
+
debug("invoking webserver with: " + Arrays.deepToString(newArgs));
|
189
|
+
main.invoke(null, new Object[] { newArgs });
|
190
|
+
}
|
191
|
+
|
192
|
+
private String[] launchWebServerArguments(Properties props) {
|
193
|
+
String[] newArgs = args;
|
194
|
+
|
195
|
+
if (props.getProperty("args") != null) {
|
196
|
+
String[] insertArgs = props.getProperty("args").split(",");
|
197
|
+
newArgs = new String[args.length + insertArgs.length];
|
198
|
+
for (int i = 0; i < insertArgs.length; i++) {
|
199
|
+
newArgs[i] = props.getProperty(insertArgs[i], "");
|
200
|
+
}
|
201
|
+
System.arraycopy(args, 0, newArgs, insertArgs.length, args.length);
|
202
|
+
}
|
203
|
+
|
204
|
+
return newArgs;
|
205
|
+
}
|
206
|
+
|
207
|
+
// JarMain overrides to make WarMain "launchable"
|
208
|
+
// e.g. java -jar rails.war -S rake db:migrate
|
209
|
+
|
210
|
+
@Override
|
211
|
+
protected String getExtractEntryPath(final JarEntry entry) {
|
212
|
+
final String name = entry.getName();
|
213
|
+
final String res;
|
214
|
+
if ( name.startsWith(WEB_INF) ) {
|
215
|
+
// WEB-INF/app/controllers/application_controller.rb ->
|
216
|
+
// app/controllers/application_controller.rb
|
217
|
+
res = name.substring(WEB_INF.length());
|
218
|
+
} else if (name.startsWith(META_INF)) {
|
219
|
+
// Keep them where they are.
|
220
|
+
res = name;
|
221
|
+
} else {
|
222
|
+
// 404.html -> public/404.html
|
223
|
+
// javascripts -> public/javascripts
|
224
|
+
res = "/public/" + name;
|
225
|
+
}
|
226
|
+
return res;
|
227
|
+
}
|
228
|
+
|
229
|
+
@Override
|
230
|
+
protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
|
231
|
+
// always extract but only return class-path entry URLs :
|
232
|
+
final URL entryURL = super.extractEntry(entry, path);
|
233
|
+
return path.endsWith(".jar") && path.startsWith("/lib/") ? entryURL : null;
|
234
|
+
}
|
235
|
+
|
236
|
+
@Override
|
237
|
+
protected int launchJRuby(final URL[] jars) throws Exception {
|
238
|
+
final Object scriptingContainer = newScriptingContainer(jars);
|
239
|
+
|
240
|
+
invokeMethod(scriptingContainer, "setArgv", (Object) executableArgv);
|
241
|
+
invokeMethod(scriptingContainer, "setCurrentDirectory", extractRoot.getAbsolutePath());
|
242
|
+
initJRubyScriptingEnv(scriptingContainer);
|
243
|
+
|
244
|
+
final Object provider = invokeMethod(scriptingContainer, "getProvider");
|
245
|
+
final Object rubyInstanceConfig = invokeMethod(provider, "getRubyInstanceConfig");
|
246
|
+
|
247
|
+
invokeMethod(rubyInstanceConfig, "setUpdateNativeENVEnabled", new Class[] { Boolean.TYPE }, false);
|
248
|
+
|
249
|
+
final CharSequence execScriptEnvPre = executableScriptEnvPrefix();
|
250
|
+
|
251
|
+
final String executablePath = locateExecutable(scriptingContainer, execScriptEnvPre);
|
252
|
+
if ( executablePath == null ) {
|
253
|
+
throw new IllegalStateException("failed to locate gem executable: '" + executable + "'");
|
254
|
+
}
|
255
|
+
invokeMethod(scriptingContainer, "setScriptFilename", executablePath);
|
256
|
+
|
257
|
+
invokeMethod(rubyInstanceConfig, "processArguments", (Object) arguments);
|
258
|
+
|
259
|
+
Object runtime = invokeMethod(scriptingContainer, "getRuntime");
|
260
|
+
|
261
|
+
debug("loading resource: " + executablePath);
|
262
|
+
Object executableInput =
|
263
|
+
new SequenceInputStream(new ByteArrayInputStream(execScriptEnvPre.toString().getBytes()),
|
264
|
+
(InputStream) invokeMethod(rubyInstanceConfig, "getScriptSource"));
|
265
|
+
|
266
|
+
debug("invoking " + executablePath + " with: " + Arrays.toString(executableArgv));
|
267
|
+
|
268
|
+
Object outcome = invokeMethod(runtime, "runFromMain",
|
269
|
+
new Class[] { InputStream.class, String.class },
|
270
|
+
executableInput, executablePath
|
271
|
+
);
|
272
|
+
return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
|
273
|
+
}
|
274
|
+
|
275
|
+
@Deprecated
|
276
|
+
protected String locateExecutable(final Object scriptingContainer) throws Exception {
|
277
|
+
if ( executable == null ) {
|
278
|
+
throw new IllegalStateException("no executable");
|
279
|
+
}
|
280
|
+
final File exec = new File(extractRoot, executable);
|
281
|
+
if ( exec.exists() ) {
|
282
|
+
return exec.getAbsolutePath();
|
283
|
+
}
|
284
|
+
else {
|
285
|
+
final String script = locateExecutableScript(executable, executableScriptEnvPrefix());
|
286
|
+
return (String) invokeMethod(scriptingContainer, "runScriptlet", script);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
protected String locateExecutable(final Object scriptingContainer, final CharSequence envPreScript)
|
291
|
+
throws Exception {
|
292
|
+
if ( executable == null ) {
|
293
|
+
throw new IllegalStateException("no executable");
|
294
|
+
}
|
295
|
+
final File exec = new File(extractRoot, executable);
|
296
|
+
if ( exec.exists() ) {
|
297
|
+
return exec.getAbsolutePath();
|
298
|
+
}
|
299
|
+
else {
|
300
|
+
final String script = locateExecutableScript(executable, envPreScript);
|
301
|
+
return (String) invokeMethod(scriptingContainer, "runScriptlet", script);
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
protected CharSequence executableScriptEnvPrefix() {
|
306
|
+
final String gemsDir = new File(extractRoot, "gems").getAbsolutePath();
|
307
|
+
final String gemfile = new File(extractRoot, "Gemfile").getAbsolutePath();
|
308
|
+
debug("setting GEM_HOME to " + gemsDir);
|
309
|
+
debug("... and BUNDLE_GEMFILE to " + gemfile);
|
310
|
+
|
311
|
+
// ideally this would look up the config.override_gem_home setting
|
312
|
+
return "ENV['GEM_HOME'] = ENV['GEM_PATH'] = '"+ gemsDir +"' \n" +
|
313
|
+
"ENV['BUNDLE_GEMFILE'] ||= '"+ gemfile +"' \n" +
|
314
|
+
"require 'uri:classloader:/META-INF/init.rb'";
|
315
|
+
}
|
316
|
+
|
317
|
+
protected String locateExecutableScript(final String executable, final CharSequence envPreScript) {
|
318
|
+
return ( envPreScript == null ? "" : envPreScript + " \n" ) +
|
319
|
+
"begin\n" + // locate the executable within gemspecs :
|
320
|
+
" require 'rubygems' unless defined?(Gem) \n" +
|
321
|
+
" begin\n" + // add bundled gems to load path :
|
322
|
+
" require 'bundler' \n" +
|
323
|
+
" rescue LoadError\n" + // bundler not used
|
324
|
+
" else\n" +
|
325
|
+
" env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] \n" + // init.rb sets ENV['RAILS_ENV'] ||= ...
|
326
|
+
" env ? Bundler.setup(:default, env) : Bundler.setup(:default) \n" +
|
327
|
+
" end if ENV_JAVA['warbler.bundler.setup'] != 'false' \n" + // java -Dwarbler.bundler.setup=false -jar my.war -S pry
|
328
|
+
" exec = '"+ executable +"' \n" +
|
329
|
+
" spec = Gem::Specification.find { |s| s.executables.include?(exec) } \n" +
|
330
|
+
" spec ? spec.bin_file(exec) : nil \n" +
|
331
|
+
// returns the full path to the executable
|
332
|
+
"rescue SystemExit => e\n" +
|
333
|
+
" e.status\n" +
|
334
|
+
"end";
|
335
|
+
}
|
336
|
+
|
337
|
+
protected void initJRubyScriptingEnv(Object scriptingContainer) throws Exception {
|
338
|
+
// for some reason, the container needs to run a scriptlet in order for it
|
339
|
+
// to be able to find the gem executables later
|
340
|
+
invokeMethod(scriptingContainer, "runScriptlet", "SCRIPTING_CONTAINER_INITIALIZED=true");
|
341
|
+
|
342
|
+
invokeMethod(scriptingContainer, "setHomeDirectory", "uri:classloader:/META-INF/jruby.home");
|
343
|
+
}
|
344
|
+
|
345
|
+
@Override
|
346
|
+
protected int start() throws Exception {
|
347
|
+
if ( executable == null ) {
|
348
|
+
try {
|
349
|
+
URL server = extractWebserver();
|
350
|
+
launchWebServer(server);
|
351
|
+
}
|
352
|
+
catch (FileNotFoundException e) {
|
353
|
+
final String msg = e.getMessage();
|
354
|
+
if ( msg != null && msg.contains("WEB-INF/webserver.jar") ) {
|
355
|
+
System.out.println("specify the -S argument followed by the bin file to run e.g. `java -jar rails.war -S rake -T` ...");
|
356
|
+
System.out.println("(or if you'd like your .war file to start a web server package it using `warbler executable war`)");
|
357
|
+
}
|
358
|
+
throw e;
|
359
|
+
}
|
360
|
+
return 0;
|
361
|
+
}
|
362
|
+
return super.start();
|
363
|
+
}
|
364
|
+
|
365
|
+
@Override
|
366
|
+
public void run() {
|
367
|
+
super.run();
|
368
|
+
if ( webroot != null ) delete(webroot.getParentFile());
|
369
|
+
}
|
370
|
+
|
371
|
+
public static void main(String[] args) {
|
372
|
+
doStart(new WarMain(args));
|
373
|
+
}
|
374
|
+
|
375
|
+
}
|
data/ext/WarblerJar.java
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2010-2012 Engine Yard, Inc.
|
3
|
+
* Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
4
|
+
* This source code is available under the MIT license.
|
5
|
+
* See the file LICENSE.txt for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import java.io.Closeable;
|
9
|
+
import java.io.File;
|
10
|
+
import java.io.FileInputStream;
|
11
|
+
import java.io.FileNotFoundException;
|
12
|
+
import java.io.FileOutputStream;
|
13
|
+
import java.io.IOException;
|
14
|
+
import java.io.InputStream;
|
15
|
+
import java.util.regex.Matcher;
|
16
|
+
import java.util.regex.Pattern;
|
17
|
+
import java.util.zip.ZipEntry;
|
18
|
+
import java.util.zip.ZipInputStream;
|
19
|
+
import java.util.zip.ZipOutputStream;
|
20
|
+
|
21
|
+
import org.jruby.Ruby;
|
22
|
+
import org.jruby.RubyArray;
|
23
|
+
import org.jruby.RubyHash;
|
24
|
+
import org.jruby.RubyModule;
|
25
|
+
import org.jruby.RubyString;
|
26
|
+
import org.jruby.anno.JRubyMethod;
|
27
|
+
import org.jruby.runtime.Block;
|
28
|
+
import org.jruby.runtime.ThreadContext;
|
29
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
30
|
+
import org.jruby.util.ByteList;
|
31
|
+
import org.jruby.util.JRubyFile;
|
32
|
+
|
33
|
+
public class WarblerJar {
|
34
|
+
public static void create(Ruby runtime) {
|
35
|
+
RubyModule task = runtime.getClassFromPath("Warbler::Jar");
|
36
|
+
task.defineAnnotatedMethods(WarblerJar.class);
|
37
|
+
}
|
38
|
+
|
39
|
+
@JRubyMethod
|
40
|
+
public static IRubyObject create_jar(ThreadContext context, IRubyObject self,
|
41
|
+
IRubyObject jar_path, IRubyObject entries) {
|
42
|
+
final Ruby runtime = context.runtime;
|
43
|
+
|
44
|
+
if (!(entries instanceof RubyHash)) {
|
45
|
+
throw runtime.newArgumentError("expected a hash for the second argument");
|
46
|
+
}
|
47
|
+
|
48
|
+
RubyHash hash = (RubyHash) entries;
|
49
|
+
try {
|
50
|
+
FileOutputStream file = newFile(jar_path);
|
51
|
+
try {
|
52
|
+
ZipOutputStream zip = new ZipOutputStream(file);
|
53
|
+
addEntries(context, zip, hash);
|
54
|
+
zip.finish();
|
55
|
+
} finally {
|
56
|
+
close(file);
|
57
|
+
}
|
58
|
+
} catch (IOException e) {
|
59
|
+
if (runtime.isDebug()) {
|
60
|
+
e.printStackTrace(runtime.getOut());
|
61
|
+
}
|
62
|
+
throw runtime.newIOErrorFromException(e);
|
63
|
+
}
|
64
|
+
|
65
|
+
return runtime.getNil();
|
66
|
+
}
|
67
|
+
|
68
|
+
@JRubyMethod
|
69
|
+
public static IRubyObject entry_in_jar(ThreadContext context, IRubyObject self,
|
70
|
+
IRubyObject jar_path, IRubyObject entry) {
|
71
|
+
final Ruby runtime = context.runtime;
|
72
|
+
try {
|
73
|
+
InputStream entryStream = getStream(jar_path.convertToString().getUnicodeValue(),
|
74
|
+
entry.convertToString().getUnicodeValue());
|
75
|
+
try {
|
76
|
+
byte[] buf = new byte[16384];
|
77
|
+
ByteList bytes = new ByteList();
|
78
|
+
int bytesRead;
|
79
|
+
while ((bytesRead = entryStream.read(buf)) != -1) {
|
80
|
+
bytes.append(buf, 0, bytesRead);
|
81
|
+
}
|
82
|
+
IRubyObject stringio = runtime.getModule("StringIO");
|
83
|
+
return stringio.callMethod(context, "new", runtime.newString(bytes));
|
84
|
+
} finally {
|
85
|
+
close(entryStream);
|
86
|
+
}
|
87
|
+
} catch (IOException e) {
|
88
|
+
if (runtime.isDebug()) {
|
89
|
+
e.printStackTrace(runtime.getOut());
|
90
|
+
}
|
91
|
+
throw runtime.newIOErrorFromException(e);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
private static void addEntries(ThreadContext context, ZipOutputStream zip, RubyHash entries) throws IOException {
|
96
|
+
RubyArray keys = entries.keys().sort(context, Block.NULL_BLOCK);
|
97
|
+
for (int i = 0; i < keys.getLength(); i++) {
|
98
|
+
IRubyObject key = keys.entry(i);
|
99
|
+
IRubyObject value = entries.op_aref(context, key);
|
100
|
+
addEntry(context, zip, key.convertToString().getUnicodeValue(), value);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
private static void addEntry(ThreadContext context, ZipOutputStream zip, String entryName, IRubyObject value) throws IOException {
|
105
|
+
if (value.respondsTo("read")) {
|
106
|
+
RubyString str = (RubyString) value.callMethod(context, "read").checkStringType();
|
107
|
+
ByteList strByteList = str.getByteList();
|
108
|
+
byte[] contents = strByteList.getUnsafeBytes();
|
109
|
+
zip.putNextEntry(new ZipEntry(entryName));
|
110
|
+
zip.write(contents, strByteList.getBegin(), strByteList.getRealSize());
|
111
|
+
} else {
|
112
|
+
File f;
|
113
|
+
if (value.isNil() || (f = getFile(value)).isDirectory()) {
|
114
|
+
zip.putNextEntry(new ZipEntry(entryName + "/"));
|
115
|
+
} else {
|
116
|
+
String path = f.getPath();
|
117
|
+
if (!f.exists()) {
|
118
|
+
path = value.convertToString().getUnicodeValue();
|
119
|
+
}
|
120
|
+
|
121
|
+
try {
|
122
|
+
InputStream inFile = getStream(path, null);
|
123
|
+
try {
|
124
|
+
zip.putNextEntry(new ZipEntry(entryName));
|
125
|
+
byte[] buf = new byte[16384];
|
126
|
+
int bytesRead;
|
127
|
+
while ((bytesRead = inFile.read(buf)) != -1) {
|
128
|
+
zip.write(buf, 0, bytesRead);
|
129
|
+
}
|
130
|
+
} finally {
|
131
|
+
close(inFile);
|
132
|
+
}
|
133
|
+
} catch (IOException e) {
|
134
|
+
System.err.println("File not found; " + path + " not in archive");
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
private static FileOutputStream newFile(IRubyObject jar_path) throws IOException {
|
141
|
+
return new FileOutputStream(getFile(jar_path));
|
142
|
+
}
|
143
|
+
|
144
|
+
private static File getFile(IRubyObject path) {
|
145
|
+
return JRubyFile.create(path.getRuntime().getCurrentDirectory(),
|
146
|
+
path.convertToString().getUnicodeValue());
|
147
|
+
}
|
148
|
+
|
149
|
+
private static void close(Closeable c) {
|
150
|
+
try {
|
151
|
+
c.close();
|
152
|
+
} catch (Exception e) {
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
private static final Pattern PROTOCOL = Pattern.compile("^[a-z][a-z0-9]+:");
|
157
|
+
|
158
|
+
private static InputStream getStream(String jar, String entry) throws IOException {
|
159
|
+
Matcher m = PROTOCOL.matcher(jar);
|
160
|
+
while (m.find()) {
|
161
|
+
jar = jar.substring(m.end());
|
162
|
+
m = PROTOCOL.matcher(jar);
|
163
|
+
}
|
164
|
+
|
165
|
+
String[] path = jar.split("!/");
|
166
|
+
InputStream stream = new FileInputStream(path[0]);
|
167
|
+
for (int i = 1; i < path.length; i++) {
|
168
|
+
stream = entryInJar(stream, path[i]);
|
169
|
+
}
|
170
|
+
|
171
|
+
if (entry == null) {
|
172
|
+
return stream;
|
173
|
+
}
|
174
|
+
|
175
|
+
return entryInJar(stream, entry);
|
176
|
+
}
|
177
|
+
|
178
|
+
private static String trimTrailingSlashes(String path) {
|
179
|
+
if (path.endsWith("/")) {
|
180
|
+
return path.substring(0, path.length() - 1);
|
181
|
+
} else {
|
182
|
+
return path;
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
private static InputStream entryInJar(InputStream jar, String entry) throws IOException {
|
187
|
+
entry = trimTrailingSlashes(entry);
|
188
|
+
|
189
|
+
ZipInputStream jstream = new ZipInputStream(jar);
|
190
|
+
ZipEntry zentry;
|
191
|
+
while ((zentry = jstream.getNextEntry()) != null) {
|
192
|
+
if (trimTrailingSlashes(zentry.getName()).equals(entry)) {
|
193
|
+
return jstream;
|
194
|
+
}
|
195
|
+
jstream.closeEntry();
|
196
|
+
}
|
197
|
+
throw new FileNotFoundException("entry '" + entry + "' not found in " + jar);
|
198
|
+
}
|
199
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2010-2012 Engine Yard, Inc.
|
3
|
+
* Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
4
|
+
* This source code is available under the MIT license.
|
5
|
+
* See the file LICENSE.txt for details.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import java.io.IOException;
|
9
|
+
import org.jruby.Ruby;
|
10
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
11
|
+
|
12
|
+
public class WarblerJarService implements BasicLibraryService {
|
13
|
+
public boolean basicLoad(final Ruby runtime) throws IOException {
|
14
|
+
WarblerJar.create(runtime);
|
15
|
+
return true;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010-2012 Engine Yard, Inc.
|
3
|
+
# Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
4
|
+
# This source code is available under the MIT license.
|
5
|
+
# See the file LICENSE.txt for details.
|
6
|
+
#++
|
7
|
+
|
8
|
+
require 'rake'
|
9
|
+
|
10
|
+
# Extension of Rake::Application that allows the +warble+ command to
|
11
|
+
# report its name properly and inject its own tasks without a
|
12
|
+
# Rakefile.
|
13
|
+
class Warbler::Application < Rake::Application
|
14
|
+
include Warbler::RakeHelper
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
Warbler.application = self
|
19
|
+
@project_loaded = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the application name and loads Warbler's own tasks
|
23
|
+
def load_rakefile
|
24
|
+
@name = 'warble'
|
25
|
+
|
26
|
+
# Load the main warbler tasks
|
27
|
+
wt = Warbler::Task.new
|
28
|
+
|
29
|
+
task :default => wt.name
|
30
|
+
|
31
|
+
desc "Generate a configuration file to customize your archive"
|
32
|
+
task :config => "#{wt.name}:config"
|
33
|
+
|
34
|
+
desc "Install Warbler tasks in your Rails application"
|
35
|
+
task :pluginize => "#{wt.name}:pluginize"
|
36
|
+
|
37
|
+
desc "Feature: package gem repository inside a jar"
|
38
|
+
task :gemjar => "#{wt.name}:gemjar"
|
39
|
+
|
40
|
+
desc "Feature: make an executable archive (runnable + an embedded web server)"
|
41
|
+
task :executable => "#{wt.name}:executable"
|
42
|
+
|
43
|
+
desc "Feature: make a runnable archive (e.g. java -jar rails.war -S rake db:migrate)"
|
44
|
+
task :runnable => "#{wt.name}:runnable"
|
45
|
+
|
46
|
+
desc "Feature: precompile all Ruby files"
|
47
|
+
task :compiled => "#{wt.name}:compiled"
|
48
|
+
|
49
|
+
desc "Display version of Warbler"
|
50
|
+
task :version => "#{wt.name}:version"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Loads the project Rakefile in a separate application
|
54
|
+
def load_project_rakefile
|
55
|
+
return if @project_loaded
|
56
|
+
# Load any application rakefiles to aid in autodetecting applications
|
57
|
+
set_application Warbler.project_application = Rake::Application.new
|
58
|
+
Rake::Application::DEFAULT_RAKEFILES.each do |rf|
|
59
|
+
if File.exist?(rf)
|
60
|
+
begin
|
61
|
+
load rf
|
62
|
+
rescue LoadError
|
63
|
+
load File.join(Dir.getwd, rf)
|
64
|
+
end
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
set_application
|
69
|
+
@project_loaded = true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Run the application: The equivalent code for the +warble+ command
|
73
|
+
# is simply <tt>Warbler::Application.run</tt>.
|
74
|
+
def self.run; new.run end
|
75
|
+
|
76
|
+
# Run the application.
|
77
|
+
def run
|
78
|
+
set_application
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
# Remap the version option to display Warbler version.
|
83
|
+
def standard_rake_options
|
84
|
+
super.map do |opt|
|
85
|
+
if opt.first == '--version'
|
86
|
+
['--version', '-V', "Display the program version.",
|
87
|
+
lambda { |value|
|
88
|
+
puts "Warbler version #{Warbler::VERSION}"
|
89
|
+
exit
|
90
|
+
}
|
91
|
+
]
|
92
|
+
else
|
93
|
+
opt
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def set_application(app = self)
|
101
|
+
Rake.application = app
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|