warbler 1.3.6 → 1.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.travis.yml +21 -8
- data/Gemfile +4 -7
- data/LICENSE.txt +1 -1
- data/README.rdoc +95 -105
- data/Rakefile +4 -3
- data/ext/JarMain.java +184 -75
- data/ext/WarMain.java +194 -65
- data/ext/WarblerJar.java +11 -1
- data/lib/warbler.rb +2 -1
- data/lib/warbler/application.rb +4 -1
- data/lib/warbler/config.rb +14 -3
- data/lib/warbler/jar.rb +10 -2
- data/lib/warbler/task.rb +7 -7
- data/lib/warbler/templates/jar.erb +3 -2
- data/lib/warbler/templates/war.erb +1 -3
- data/lib/warbler/traits/bundler.rb +11 -3
- data/lib/warbler/traits/gemspec.rb +1 -1
- data/lib/warbler/traits/jar.rb +5 -2
- data/lib/warbler/traits/nogemspec.rb +7 -2
- data/lib/warbler/traits/war.rb +13 -2
- data/lib/warbler/version.rb +1 -1
- data/lib/warbler/web_server.rb +2 -2
- data/lib/warbler_jar.jar +0 -0
- data/spec/sample_war/Rakefile +5 -1
- data/spec/sample_war/lib/ruby_one_nine.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/warbler/application_spec.rb +5 -5
- data/spec/warbler/bundler_spec.rb +47 -20
- data/spec/warbler/config_spec.rb +3 -1
- data/spec/warbler/jar_spec.rb +25 -4
- data/spec/warbler/task_spec.rb +17 -1
- data/warble.rb +6 -2
- data/warbler.gemspec +21 -21
- metadata +286 -257
- data/Gemfile.lock +0 -41
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,12 +1,25 @@
|
|
1
1
|
rvm:
|
2
|
-
-
|
3
|
-
-
|
4
|
-
- jruby-18mode
|
5
|
-
# NOTE: seems to be failing on Travis the same way as jruby-rack did, stack
|
6
|
-
# seems to overflow when gems are loaded (does not happen locally :() :
|
7
|
-
# SystemStackError: stack level too deep
|
8
|
-
# ./spec/../lib/warbler/traits/bundler.rb:30:in `after_configure'
|
9
|
-
#- jruby-19mode
|
2
|
+
- jruby
|
3
|
+
- jruby-head
|
10
4
|
branches:
|
11
5
|
only:
|
12
6
|
- master
|
7
|
+
env:
|
8
|
+
- JRUBY_OPTS="--1.8 --server -Xcext.enabled=false -Xcompile.invokedynamic=false"
|
9
|
+
- JRUBY_OPTS="--1.9 --server -Xcext.enabled=false -Xcompile.invokedynamic=false"
|
10
|
+
matrix:
|
11
|
+
include:
|
12
|
+
- rvm: 1.8.7
|
13
|
+
env: ''
|
14
|
+
- rvm: 1.9.2
|
15
|
+
env: ''
|
16
|
+
- rvm: 1.9.3
|
17
|
+
env: ''
|
18
|
+
notifications:
|
19
|
+
irc:
|
20
|
+
channels:
|
21
|
+
- "irc.freenode.org#jruby"
|
22
|
+
on_success: change
|
23
|
+
on_failure: always
|
24
|
+
template:
|
25
|
+
- "%{repository} (%{branch}:%{commit} by %{author}): %{message} (%{build_url})"
|
data/Gemfile
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
source "http://rubygems.org/"
|
2
2
|
|
3
|
-
|
4
|
-
gem "rubyzip"
|
5
|
-
gem "jruby-jars"
|
6
|
-
gem "jruby-rack"
|
3
|
+
gemspec
|
7
4
|
|
8
5
|
group :development do
|
9
6
|
gem "jruby-openssl", :platform => :jruby
|
10
|
-
gem "rspec"
|
7
|
+
gem "rspec", ">= 2.8.0"
|
11
8
|
gem "diff-lcs"
|
12
|
-
gem "rcov", ">= 0.9.8"
|
13
|
-
gem "rdoc"
|
9
|
+
gem "rcov", ">= 0.9.8", :platform => :mri_18
|
10
|
+
gem "rdoc", ">= 2.4.2"
|
14
11
|
gem "childprocess", :platform => :mri
|
15
12
|
end
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
= Warbler
|
1
|
+
= Warbler {<img src="https://travis-ci.org/jruby/warbler.png" />}[https://travis-ci.org/jruby/warbler]
|
2
2
|
|
3
|
-
Warbler is a gem to make a Java jar or war file out of any Ruby,
|
4
|
-
|
5
|
-
|
6
|
-
deployment to a Java environment.
|
3
|
+
Warbler is a gem to make a Java jar or war file out of any Ruby, Rails or Rack
|
4
|
+
application. Warbler provides a minimal, flexible, Ruby-like way to bundle up
|
5
|
+
all of your application files for deployment to a Java environment.
|
7
6
|
|
8
7
|
Warbler provides a sane set of out-of-the box defaults that should allow most
|
9
8
|
Ruby applications to assemble and Just Work.
|
@@ -14,7 +13,8 @@ Ruby applications to assemble and Just Work.
|
|
14
13
|
|
15
14
|
2. Run warbler in the top directory of your application: <tt>warble</tt>.
|
16
15
|
|
17
|
-
3a. For a web project, deploy your myapp.war file to your favorite Java
|
16
|
+
3a. For a web project, deploy your +myapp.war+ file to your favorite Java
|
17
|
+
application server.
|
18
18
|
|
19
19
|
3b. For a standalone applications, just run it: <tt>java -jar myapp.jar</tt>.
|
20
20
|
|
@@ -26,22 +26,24 @@ defined tasks.
|
|
26
26
|
$ warble -T
|
27
27
|
warble compiled # Feature: precompile all Ruby files
|
28
28
|
warble config # Generate a configuration file to customize your archive
|
29
|
-
warble executable # Feature: make an executable archive
|
30
|
-
warble gemjar # Feature: package gem repository inside a
|
29
|
+
warble executable # Feature: make an executable archive (runnable + an emb...
|
30
|
+
warble gemjar # Feature: package gem repository inside a jar
|
31
31
|
warble pluginize # Install Warbler tasks in your Rails application
|
32
|
+
warble runnable # Feature: make a runnable archive (e.g. java -jar rails...
|
32
33
|
warble version # Display version of Warbler
|
33
34
|
warble war # Create the project war file
|
34
35
|
warble war:clean # Remove the project war file
|
35
36
|
warble war:debug # Dump diagnostic information
|
36
37
|
|
38
|
+
|
37
39
|
Type <tt>warble</tt> to create the jar or war file.
|
38
40
|
|
39
41
|
== Features
|
40
42
|
|
41
|
-
Warbler "features" are small Rake tasks that run before the creation
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
Warbler "features" are small Rake tasks that run before the creation of the war
|
44
|
+
file and make manipulations to the archive structure. For instance, the
|
45
|
+
+executable+ feature makes your war file capable of running on its own,
|
46
|
+
without a servlet container (using an embedded web server) :
|
45
47
|
|
46
48
|
warble executable war
|
47
49
|
|
@@ -53,83 +55,81 @@ or configure them in <tt>config/warble.rb</tt> to always be used.
|
|
53
55
|
|
54
56
|
config.features = %w(FEATURE)
|
55
57
|
|
56
|
-
Currently,
|
58
|
+
Currently, the following features are available :
|
57
59
|
|
58
60
|
* +gemjar+: This bundles all gems into a single gem file to reduce the
|
59
61
|
number of files in the .war. This is mostly useful for Google
|
60
62
|
AppEngine where the number of files per application has a limit.
|
61
63
|
(Note: not applicable for jar-based applications.)
|
62
|
-
* +
|
63
|
-
|
64
|
-
|
64
|
+
* +runnable+: This makes a (standard Java) runnable .war archive thus you can
|
65
|
+
execute binary bundled (gem) commands e.g. "rake". You should use the -S
|
66
|
+
switch to specify the binary followed by any arguments in takes e.g.
|
67
|
+
<tt>java -jar myrailsapp.war -S rake db:migrate</tt>.
|
68
|
+
* +executable+: This bundles an embedded web server into the .war so that it
|
69
|
+
can either be deployed into a traditional java web server or run as a
|
70
|
+
standalone application using <tt>java -jar myapp.war</tt>.
|
65
71
|
(Note: jar-based applications are executable by default.)
|
66
|
-
* +compiled+: This uses +jrubyc+ to precompile all .rb files in your
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
require __FILE__.sub(/\.rb$/, '.class')
|
72
|
+
* +compiled+: This uses +jrubyc+ to precompile all .rb files in your application
|
73
|
+
to .class files and includes those in the .war instead of the Ruby sources.
|
74
|
+
NOTE: The war file will still contain .rb files, but they will be short stubs
|
75
|
+
containing the following code : <tt>load __FILE__.sub(/\.rb$/, '.class')</tt>
|
72
76
|
|
73
|
-
Features may form the basis for a third-party plugin system in the
|
74
|
-
|
77
|
+
Features may form the basis for a third-party plugin system (in the future)
|
78
|
+
if there is demand.
|
75
79
|
|
76
|
-
NOTE: Feature tasks must be included in the same command invocation
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
sources included. Always make sure you run
|
80
|
+
NOTE: Feature tasks must be included in the same command invocation and
|
81
|
+
inserted before the +war+ task in order to take effect. For example,
|
82
|
+
<tt>warble compiled; warble war</tt> does not compile and obfuscate +.rb+
|
83
|
+
sources because the second invocation of +warble+ does not run the +compiled+
|
84
|
+
feature and creates a basic war with the sources included, make sure you run :
|
82
85
|
|
83
86
|
warble compiled war
|
84
87
|
|
85
|
-
or, if it's important that the war always be compiled, use the option
|
86
|
-
|
88
|
+
or, if it's important that the war always be compiled, use the option above to
|
89
|
+
put the feature in your <tt>config/warble.rb</tt>.
|
87
90
|
|
88
91
|
== War or Jar?
|
89
92
|
|
90
|
-
War-based projects are for Rails, Merb, or Rack-based web
|
91
|
-
|
92
|
-
|
93
|
-
The presence of these files are used to determine if the project
|
94
|
-
|
95
|
-
for the project.
|
93
|
+
War-based projects are for Rails, Merb, or Rack-based web applications.
|
94
|
+
They usually contain a +config/environment.rb+ file, a +config/init.rb+ file,
|
95
|
+
or a +config.ru+ file.
|
96
|
+
The presence of these files are used to determine if the project is a web
|
97
|
+
application, and thus a Java EE compatible war file is built for the project.
|
96
98
|
|
97
|
-
Jar-based projects are for standalone Ruby applications. Usually a
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
script.
|
99
|
+
Jar-based projects are for standalone Ruby applications. Usually a Ruby
|
100
|
+
application has a launcher script in the +bin+ directory and Ruby code
|
101
|
+
in the <tt>lib</tt> directory. Warbler packages the application so that
|
102
|
+
<tt>java -jar myapp.jar</tt> runs the launcher script.
|
102
103
|
|
103
104
|
== Jar Files
|
104
105
|
|
105
106
|
=== Gem Specification (gemspec) Files
|
106
107
|
|
107
|
-
If your project has a <tt>.gemspec</tt> file in the top directory, it
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
attributes:
|
108
|
+
If your project has a <tt>.gemspec</tt> file in the top directory, it will be
|
109
|
+
used to configure the project's dependencies, launcher script, require paths,
|
110
|
+
and the files to be included in the archive. For best results make sure your
|
111
|
+
gemspec specifies all of the following attributes:
|
112
112
|
|
113
113
|
* +executables+
|
114
114
|
* +require_paths+
|
115
115
|
* runtime dependencies added with +add_dependency+
|
116
116
|
|
117
|
-
If your project do not have a <tt>.gemspec</tt>, Warbler will attempt
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
If your project do not have a <tt>.gemspec</tt>, Warbler will attempt to guess
|
118
|
+
the launcher from the contents of the <tt>bin</tt> directory and use the
|
119
|
+
<tt>lib</tt> directory as the lone require path. All files in the project
|
120
|
+
will be included in the archive.
|
121
121
|
|
122
122
|
=== Bundler
|
123
123
|
|
124
|
-
Applications that use Bundler[http://gembundler.com/], detected via
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
124
|
+
Applications that use Bundler[http://gembundler.com/], detected via presence of
|
125
|
+
a +Gemfile+, will have the gems packaged up into the archive along with the
|
126
|
+
Gemfile. The Bundler groups named ":development", ":test" and ":assets" will be
|
127
|
+
excluded by default, unless you specify with <tt>config.bundle_without</tt> in
|
128
|
+
+config/warble.rb+.
|
129
129
|
|
130
|
-
Warbler supports Bundler for gems and git repositories, but not for
|
131
|
-
|
132
|
-
|
130
|
+
Warbler supports Bundler for gems and git repositories, but not for plain path
|
131
|
+
components. Warbler will warn when a +:path+ component is found in the +Gemfile+
|
132
|
+
and will refuse to include it in the archive.
|
133
133
|
|
134
134
|
== War Files
|
135
135
|
|
@@ -138,42 +138,34 @@ found in the +Gemfile+ and will refuse to include it in the archive.
|
|
138
138
|
Rails applications are detected automatically and configured appropriately.
|
139
139
|
The following items are set up for you:
|
140
140
|
|
141
|
-
* Your application runs in the +production+ environment by default.
|
141
|
+
* Your application runs in the +production+ environment by default.
|
142
|
+
Change it in +config/warble.rb+ (see below).
|
142
143
|
* The Rails gem is packaged if you haven't vendored Rails (Rails <= 2.x).
|
143
|
-
* Other gems configured in Rails.configuration.gems are packaged (
|
144
|
-
* Multi-thread-safe execution (as introduced in Rails 2.2) is detected and
|
145
|
-
|
146
|
-
=== Merb applications
|
147
|
-
|
148
|
-
Merb applications are detected automatically, and the merb-core gem and its
|
149
|
-
dependencies are packaged.
|
144
|
+
* Other gems configured in Rails.configuration.gems are packaged (2.1 - 2.3)
|
145
|
+
* Multi-thread-safe execution (as introduced in Rails 2.2) is detected and
|
146
|
+
runtime pooling is disabled.
|
150
147
|
|
151
148
|
=== Other Rack-based applications
|
152
149
|
|
153
|
-
If you have a +config.ru+ file in the top directory or one of the
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
<tt>ENV['RACK_ENV']</tt> will be set to +production+.
|
150
|
+
If you have a +config.ru+ file in the top directory or one of the immediate
|
151
|
+
subdirectories of your application, it will be included and used as the rackup
|
152
|
+
script for your Rack-based application. You will probably need to specify
|
153
|
+
framework and application gems in +config/warble.rb+ unless you're using Bundler
|
154
|
+
to manage your gems. <tt>ENV['RACK_ENV']</tt> will be set to +production+.
|
159
155
|
|
160
156
|
See {the examples in the jruby-rack project}[http://github.com/jruby/jruby-rack/tree/master/examples/]
|
161
157
|
of how to configure Warbler to package Camping and Sinatra apps.
|
162
158
|
|
163
159
|
=== Configuration auto-detect notes
|
164
160
|
|
165
|
-
* Warbler will load the +environment+ Rake task in a Rails application
|
166
|
-
to
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
needed. Until then, you will have to specify them in
|
174
|
-
+config/warble.rb+. See below.
|
175
|
-
* Is it possible to more generally detect what gems an application
|
176
|
-
uses? Gem.loaded_specs is available, but the application needs to be
|
161
|
+
* Warbler will load the +environment+ Rake task in a Rails application to try
|
162
|
+
to detect some configuration. If you don't have database access in the
|
163
|
+
environment where you package your application, you may wish to set
|
164
|
+
<tt>Warbler.framework_detection = false</tt> at the top of +config.rb+.
|
165
|
+
In this case you may need to specify additional details such as booter, gems
|
166
|
+
and other settings that would normally be gleaned from the app configuration.
|
167
|
+
* Is it possible to more generally detect what gems an application uses?
|
168
|
+
<tt>Gem.loaded_specs</tt> is available, but the application needs to be
|
177
169
|
loaded first before its contents are reliable.
|
178
170
|
|
179
171
|
== Custom configuration
|
@@ -185,17 +177,16 @@ doing this. With the gem, simply run
|
|
185
177
|
|
186
178
|
warble config
|
187
179
|
|
188
|
-
Finally, edit the config/warble.rb to your taste. The generated
|
189
|
-
|
190
|
-
and default values.
|
180
|
+
Finally, edit the +config/warble.rb+ to your taste. The generated file is
|
181
|
+
fully-documented with the available options and default values.
|
191
182
|
|
192
183
|
=== War layout
|
193
184
|
|
194
185
|
The default configuration puts application files (+app+, +config+, +lib+,
|
195
|
-
+log+, +vendor+, +tmp+) under the .war file's WEB-INF directory, and files in
|
186
|
+
+log+, +vendor+, +tmp+) under the .war file's +WEB-INF+ directory, and files in
|
196
187
|
+public+ in the root of the .war file. Any Java .jar files stored in lib will
|
197
|
-
automatically be placed in WEB-INF/lib for placement on the web app's
|
198
|
-
|
188
|
+
automatically be placed in +WEB-INF/lib+ for placement on the web app's
|
189
|
+
class-path.
|
199
190
|
|
200
191
|
=== Web.xml
|
201
192
|
|
@@ -220,27 +211,27 @@ For more information on configuration, see Warbler::Config.
|
|
220
211
|
|
221
212
|
== Rakefile integration
|
222
213
|
|
223
|
-
If you'd like to control Warbler from your own project's Rakefile
|
224
|
-
simply add the following code somewhere in the Rakefile:
|
214
|
+
If you'd like to control Warbler from your own project's +Rakefile+,
|
215
|
+
simply add the following code somewhere in the +Rakefile+ :
|
225
216
|
|
226
217
|
require 'warbler'
|
227
218
|
Warbler::Task.new
|
228
219
|
|
229
|
-
If you're using Bundler, you'll want to add Warbler to your Gemfile:
|
220
|
+
If you're using Bundler, you'll want to add Warbler to your +Gemfile+ :
|
230
221
|
|
231
222
|
group :development do
|
232
223
|
gem "warbler"
|
233
224
|
end
|
234
225
|
|
235
|
-
Now you should be able to invoke
|
226
|
+
Now you should be able to invoke <tt>rake war</tt> to create your war file.
|
236
227
|
|
237
228
|
== Troubleshooting
|
238
229
|
|
239
|
-
If Warbler isn't packaging the files you were expecting, use the
|
240
|
-
|
230
|
+
If Warbler isn't packaging the files you were expecting, use the +war:debug+
|
231
|
+
task to give you more insight into what's going on.
|
241
232
|
|
242
233
|
If you think you found a bug, please file one at
|
243
|
-
|
234
|
+
https://github.com/jruby/warbler/issues.
|
244
235
|
|
245
236
|
== Source
|
246
237
|
|
@@ -248,22 +239,21 @@ You can get the Warbler source using Git, in any of the following ways:
|
|
248
239
|
|
249
240
|
git clone git://git.caldersphere.net/warbler.git
|
250
241
|
git clone git://github.com/jruby/warbler.git
|
251
|
-
git clone git://kenai.com/warbler~main
|
252
242
|
|
253
243
|
You can also download a tarball of Warbler source at
|
254
244
|
https://github.com/jruby/warbler/tarball/master.
|
255
245
|
|
256
246
|
== Development
|
257
247
|
|
258
|
-
You can develop Warbler with any implementation of Ruby. To write
|
259
|
-
|
260
|
-
and run "bundle install" once.
|
248
|
+
You can develop Warbler with any implementation of Ruby. To write Warbler code
|
249
|
+
and run specs, you need to have Bundler installed and run <tt>bundle</tt> once.
|
261
250
|
|
262
|
-
After that, simply run
|
251
|
+
After that, simply run <tt>rake</tt>.
|
263
252
|
|
264
253
|
== License
|
265
254
|
|
266
255
|
Warbler is provided under the terms of the MIT license.
|
267
256
|
|
268
|
-
Warbler (c) 2010-
|
257
|
+
Warbler (c) 2010-2013 Engine Yard, Inc.
|
258
|
+
|
269
259
|
Warbler (c) 2007-2009 Sun Microsystems, Inc.
|
data/Rakefile
CHANGED
@@ -7,9 +7,10 @@
|
|
7
7
|
|
8
8
|
begin
|
9
9
|
require 'bundler/setup'
|
10
|
-
rescue LoadError
|
11
|
-
|
12
|
-
puts "Please install
|
10
|
+
rescue LoadError => e
|
11
|
+
require('rubygems') && retry
|
12
|
+
puts "Please `gem install bundler' and run `bundle install' to ensure you have all dependencies"
|
13
|
+
raise e
|
13
14
|
end
|
14
15
|
|
15
16
|
require 'bundler/gem_helper'
|
data/ext/JarMain.java
CHANGED
@@ -6,143 +6,252 @@
|
|
6
6
|
*/
|
7
7
|
|
8
8
|
import java.io.File;
|
9
|
+
import java.io.IOException;
|
9
10
|
import java.io.FileOutputStream;
|
10
11
|
import java.io.InputStream;
|
12
|
+
import java.lang.reflect.InvocationTargetException;
|
11
13
|
import java.lang.reflect.Method;
|
14
|
+
import java.net.URI;
|
15
|
+
import java.net.URISyntaxException;
|
12
16
|
import java.net.URL;
|
13
17
|
import java.net.URLClassLoader;
|
14
18
|
import java.util.ArrayList;
|
15
19
|
import java.util.Arrays;
|
16
20
|
import java.util.Enumeration;
|
21
|
+
import java.util.HashMap;
|
17
22
|
import java.util.List;
|
23
|
+
import java.util.Map;
|
18
24
|
import java.util.jar.JarEntry;
|
19
25
|
import java.util.jar.JarFile;
|
20
26
|
|
21
27
|
public class JarMain implements Runnable {
|
22
|
-
|
28
|
+
|
29
|
+
static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
final boolean debug = isDebug();
|
32
|
+
|
33
|
+
protected final String[] args;
|
34
|
+
protected final String archive;
|
35
|
+
private final String path;
|
36
|
+
|
37
|
+
protected File extractRoot;
|
28
38
|
|
29
|
-
|
39
|
+
JarMain(String[] args) {
|
30
40
|
this.args = args;
|
31
41
|
URL mainClass = getClass().getResource(MAIN);
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
42
|
+
try {
|
43
|
+
this.path = mainClass.toURI().getSchemeSpecificPart();
|
44
|
+
}
|
45
|
+
catch (URISyntaxException e) {
|
46
|
+
throw new RuntimeException(e);
|
47
|
+
}
|
48
|
+
archive = this.path.replace("!" + MAIN, "").replace("file:", "");
|
49
|
+
|
38
50
|
Runtime.getRuntime().addShutdownHook(new Thread(this));
|
39
51
|
}
|
52
|
+
|
53
|
+
protected URL[] extractArchive() throws Exception {
|
54
|
+
final JarFile jarFile = new JarFile(archive);
|
55
|
+
try {
|
56
|
+
Map<String, JarEntry> jarNames = new HashMap<String, JarEntry>();
|
57
|
+
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
|
58
|
+
JarEntry entry = e.nextElement();
|
59
|
+
String extractPath = getExtractEntryPath(entry);
|
60
|
+
if ( extractPath != null ) jarNames.put(extractPath, entry);
|
61
|
+
}
|
62
|
+
|
63
|
+
extractRoot = File.createTempFile("jruby", "extract");
|
64
|
+
extractRoot.delete(); extractRoot.mkdirs();
|
40
65
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
String name = eje.nextElement().getName();
|
46
|
-
if (name.startsWith("META-INF/lib") && name.endsWith(".jar")) {
|
47
|
-
jarNames.add("/" + name);
|
66
|
+
final List<URL> urls = new ArrayList<URL>();
|
67
|
+
for (Map.Entry<String, JarEntry> e : jarNames.entrySet()) {
|
68
|
+
URL entryURL = extractEntry(e.getValue(), e.getKey());
|
69
|
+
if (entryURL != null) urls.add( entryURL );
|
48
70
|
}
|
71
|
+
return (URL[]) urls.toArray(new URL[urls.size()]);
|
49
72
|
}
|
50
|
-
|
51
|
-
|
52
|
-
for (String name : jarNames) {
|
53
|
-
urls.add(extractJar(name));
|
73
|
+
finally {
|
74
|
+
jarFile.close();
|
54
75
|
}
|
55
|
-
|
56
|
-
return (URL[]) urls.toArray(new URL[urls.size()]);
|
57
76
|
}
|
58
77
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
78
|
+
protected String getExtractEntryPath(final JarEntry entry) {
|
79
|
+
final String name = entry.getName();
|
80
|
+
if ( name.startsWith("META-INF/lib") && name.endsWith(".jar") ) {
|
81
|
+
return name.substring(name.lastIndexOf("/") + 1);
|
82
|
+
}
|
83
|
+
return null; // do not extract entry
|
84
|
+
}
|
85
|
+
|
86
|
+
protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
|
87
|
+
final File file = new File(extractRoot, path);
|
88
|
+
if ( entry.isDirectory() ) {
|
89
|
+
file.mkdirs();
|
90
|
+
return null;
|
91
|
+
}
|
92
|
+
final String entryPath = entryPath(entry.getName());
|
93
|
+
final InputStream entryStream;
|
94
|
+
try {
|
95
|
+
entryStream = new URI("jar", entryPath, null).toURL().openStream();
|
96
|
+
}
|
97
|
+
catch (IllegalArgumentException e) {
|
98
|
+
// TODO gems '%' file name "encoding" ?!
|
99
|
+
debug("failed to open jar:" + entryPath + " skipping entry: " + entry.getName(), e);
|
100
|
+
return null;
|
101
|
+
}
|
102
|
+
final File parent = file.getParentFile();
|
103
|
+
if ( parent != null ) parent.mkdirs();
|
104
|
+
FileOutputStream outStream = new FileOutputStream(file);
|
105
|
+
final byte[] buf = new byte[65536];
|
65
106
|
try {
|
66
|
-
byte[] buf = new byte[65536];
|
67
107
|
int bytesRead = 0;
|
68
|
-
while ((bytesRead =
|
108
|
+
while ((bytesRead = entryStream.read(buf)) != -1) {
|
69
109
|
outStream.write(buf, 0, bytesRead);
|
70
110
|
}
|
71
|
-
}
|
72
|
-
|
111
|
+
}
|
112
|
+
finally {
|
113
|
+
entryStream.close();
|
73
114
|
outStream.close();
|
115
|
+
file.deleteOnExit();
|
74
116
|
}
|
75
|
-
debug(
|
76
|
-
return
|
117
|
+
if (false) debug(entry.getName() + " extracted to " + file.getPath());
|
118
|
+
return file.toURI().toURL();
|
119
|
+
}
|
120
|
+
|
121
|
+
protected String entryPath(String name) {
|
122
|
+
if ( ! name.startsWith("/") ) name = "/" + name;
|
123
|
+
return path.replace(MAIN, name);
|
77
124
|
}
|
78
125
|
|
79
|
-
|
126
|
+
protected Object newScriptingContainer(final URL[] jars) throws Exception {
|
80
127
|
System.setProperty("org.jruby.embed.class.path", "");
|
81
|
-
|
128
|
+
ClassLoader loader = new URLClassLoader(jars);
|
82
129
|
Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, loader);
|
83
130
|
Object scriptingContainer = scriptingContainerClass.newInstance();
|
131
|
+
debug("scripting container class loader urls: " + Arrays.toString(jars));
|
132
|
+
invokeMethod(scriptingContainer, "setArgv", (Object) args);
|
133
|
+
invokeMethod(scriptingContainer, "setClassLoader", new Class[] { ClassLoader.class }, loader);
|
134
|
+
return scriptingContainer;
|
135
|
+
}
|
136
|
+
|
137
|
+
protected int launchJRuby(final URL[] jars) throws Exception {
|
138
|
+
final Object scriptingContainer = newScriptingContainer(jars);
|
139
|
+
debug("invoking " + archive + " with: " + Arrays.deepToString(args));
|
140
|
+
Object outcome = invokeMethod(scriptingContainer, "runScriptlet", launchScript());
|
141
|
+
return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
|
142
|
+
}
|
84
143
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
"end"
|
100
|
-
})).intValue();
|
144
|
+
protected String launchScript() {
|
145
|
+
return
|
146
|
+
"begin\n" +
|
147
|
+
" require 'META-INF/init.rb'\n" +
|
148
|
+
" require 'META-INF/main.rb'\n" +
|
149
|
+
" 0\n" +
|
150
|
+
"rescue SystemExit => e\n" +
|
151
|
+
" e.status\n" +
|
152
|
+
"end";
|
153
|
+
}
|
154
|
+
|
155
|
+
protected int start() throws Exception {
|
156
|
+
final URL[] jars = extractArchive();
|
157
|
+
return launchJRuby(jars);
|
101
158
|
}
|
102
159
|
|
103
|
-
|
104
|
-
|
105
|
-
return launchJRuby(u);
|
160
|
+
protected void debug(String msg) {
|
161
|
+
debug(msg, null);
|
106
162
|
}
|
107
163
|
|
108
|
-
|
109
|
-
if (debug)
|
110
|
-
|
164
|
+
protected void debug(String msg, Throwable t) {
|
165
|
+
if (debug) System.out.println(msg);
|
166
|
+
if (debug && t != null) t.printStackTrace(System.out);
|
167
|
+
}
|
168
|
+
|
169
|
+
protected void delete(File f) {
|
170
|
+
try {
|
171
|
+
if (f.isDirectory() && !isSymlink(f)) {
|
172
|
+
File[] children = f.listFiles();
|
173
|
+
for (int i = 0; i < children.length; i++) {
|
174
|
+
delete(children[i]);
|
175
|
+
}
|
176
|
+
}
|
177
|
+
f.delete();
|
178
|
+
} catch (IOException e) {
|
179
|
+
System.err.println("error: " + e.toString());
|
111
180
|
}
|
112
181
|
}
|
113
182
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
183
|
+
protected boolean isSymlink(File file) throws IOException {
|
184
|
+
if (file == null)
|
185
|
+
throw new NullPointerException("File must not be null");
|
186
|
+
File canon;
|
187
|
+
if (file.getParent() == null) {
|
188
|
+
canon = file;
|
189
|
+
} else {
|
190
|
+
File canonDir = file.getParentFile().getCanonicalFile();
|
191
|
+
canon = new File(canonDir, file.getName());
|
192
|
+
}
|
193
|
+
return !canon.getCanonicalFile().equals(canon.getAbsoluteFile());
|
122
194
|
}
|
123
195
|
|
124
196
|
public void run() {
|
125
|
-
delete(extractRoot);
|
197
|
+
if ( extractRoot != null ) delete(extractRoot);
|
126
198
|
}
|
127
199
|
|
128
200
|
public static void main(String[] args) {
|
201
|
+
doStart(new JarMain(args));
|
202
|
+
}
|
203
|
+
|
204
|
+
protected static void doStart(final JarMain main) {
|
129
205
|
try {
|
130
|
-
int exit =
|
131
|
-
System.exit(exit);
|
206
|
+
int exit = main.start();
|
207
|
+
if(isSystemExitEnabled()) System.exit(exit);
|
132
208
|
} catch (Exception e) {
|
209
|
+
System.err.println("error: " + e.toString());
|
133
210
|
Throwable t = e;
|
134
211
|
while (t.getCause() != null && t.getCause() != t) {
|
135
212
|
t = t.getCause();
|
136
213
|
}
|
137
|
-
|
138
214
|
if (isDebug()) {
|
139
215
|
t.printStackTrace();
|
140
216
|
}
|
141
217
|
System.exit(1);
|
142
218
|
}
|
143
219
|
}
|
220
|
+
|
221
|
+
protected static Object invokeMethod(final Object self, final String name, final Object... args)
|
222
|
+
throws NoSuchMethodException, IllegalAccessException, Exception {
|
223
|
+
|
224
|
+
final Class[] signature = new Class[args.length];
|
225
|
+
for ( int i = 0; i < args.length; i++ ) signature[i] = args[i].getClass();
|
226
|
+
return invokeMethod(self, name, signature, args);
|
227
|
+
}
|
144
228
|
|
145
|
-
|
146
|
-
|
229
|
+
protected static Object invokeMethod(final Object self, final String name, final Class[] signature, final Object... args)
|
230
|
+
throws NoSuchMethodException, IllegalAccessException, Exception {
|
231
|
+
Method method = self.getClass().getDeclaredMethod(name, signature);
|
232
|
+
try {
|
233
|
+
return method.invoke(self, args);
|
234
|
+
}
|
235
|
+
catch (InvocationTargetException e) {
|
236
|
+
Throwable target = e.getTargetException();
|
237
|
+
if (target instanceof Exception) {
|
238
|
+
throw (Exception) target;
|
239
|
+
}
|
240
|
+
throw e;
|
241
|
+
}
|
147
242
|
}
|
243
|
+
|
244
|
+
static boolean isDebug() {
|
245
|
+
return Boolean.getBoolean("warbler.debug");
|
246
|
+
}
|
247
|
+
|
248
|
+
/**
|
249
|
+
* if warbler.skip_system_exit system property is defined, we will not
|
250
|
+
* call System.exit in the normal flow. System.exit can cause problems
|
251
|
+
* for wrappers like procrun
|
252
|
+
*/
|
253
|
+
private static boolean isSystemExitEnabled(){
|
254
|
+
return System.getProperty("warbler.skip_system_exit") == null; //omission enables System.exit use
|
255
|
+
}
|
256
|
+
|
148
257
|
}
|