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/README.rdoc
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
= Warbler {<img src="https://badge.fury.io/rb/warbler.svg" alt="Gem Version" />}[http://badge.fury.io/rb/warbler] {<img src="https://github.com/jruby/warbler/actions/workflows/ci.yml/badge.svg" alt="Build Status" />}[https://github.com/jruby/warbler/actions/workflows/ci.yml]
|
2
|
+
|
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.
|
6
|
+
|
7
|
+
Warbler provides a sane set of out-of-the box defaults that should allow most
|
8
|
+
Ruby applications to assemble and Just Work.
|
9
|
+
|
10
|
+
Version 2.x of Warbler supports versions of JRuby from 9.2.0.0 and up.
|
11
|
+
|
12
|
+
Version 1.4.x of Warbler supports versions of JRuby up to 1.7.x. The {1.x-dev branch}[https://github.com/jruby/warbler/tree/1.x-dev] is the working code for this.
|
13
|
+
|
14
|
+
== Getting Started
|
15
|
+
|
16
|
+
1. Install the gem: <tt>gem install warbler</tt>.
|
17
|
+
|
18
|
+
2. Run warbler in the top directory of your application: <tt>warble</tt>.
|
19
|
+
|
20
|
+
3. Choose one:
|
21
|
+
|
22
|
+
* For a web project, deploy your +myapp.war+ file to your favorite Java application server.
|
23
|
+
|
24
|
+
* For a standalone applications, just run it: <tt>java -jar myapp.jar</tt>.
|
25
|
+
|
26
|
+
== Usage
|
27
|
+
|
28
|
+
Warbler's +warble+ command is just a small wrapper around Rake with internally
|
29
|
+
defined tasks.
|
30
|
+
|
31
|
+
$ warble -T
|
32
|
+
warble compiled # Feature: precompile all Ruby files
|
33
|
+
warble config # Generate a configuration file to customize your archive
|
34
|
+
warble executable # Feature: make an executable archive (runnable + an emb...
|
35
|
+
warble gemjar # Feature: package gem repository inside a jar
|
36
|
+
warble pluginize # Install Warbler tasks in your Rails application
|
37
|
+
warble runnable # Feature: make a runnable archive (e.g. java -jar rails...
|
38
|
+
warble version # Display version of Warbler
|
39
|
+
warble war # Create the project war file
|
40
|
+
warble war:clean # Remove the project war file
|
41
|
+
warble war:debug # Dump diagnostic information
|
42
|
+
|
43
|
+
|
44
|
+
Type <tt>warble</tt> to create the jar or war file.
|
45
|
+
|
46
|
+
== Features
|
47
|
+
|
48
|
+
Warbler "features" are small Rake tasks that run before the creation of the war
|
49
|
+
file and make manipulations to the archive structure. For instance, the
|
50
|
+
+executable+ feature makes your war file capable of running on its own,
|
51
|
+
without a servlet container (using an embedded web server) :
|
52
|
+
|
53
|
+
warble executable war
|
54
|
+
|
55
|
+
You can either add features to the warbler command line:
|
56
|
+
|
57
|
+
warble FEATURE war
|
58
|
+
|
59
|
+
or configure them in <tt>config/warble.rb</tt> to always be used.
|
60
|
+
|
61
|
+
config.features = %w(FEATURE)
|
62
|
+
|
63
|
+
Currently, the following features are available :
|
64
|
+
|
65
|
+
* +gemjar+: This bundles all gems into a single gem file to reduce the
|
66
|
+
number of files in the .war. This is mostly useful for Google
|
67
|
+
AppEngine where the number of files per application has a limit.
|
68
|
+
(Note: not applicable for jar-based applications.)
|
69
|
+
* +runnable+: This makes a (standard Java) runnable .war archive thus you can
|
70
|
+
execute binary bundled (gem) commands e.g. "rake". You should use the -S
|
71
|
+
switch to specify the binary followed by any arguments in takes e.g.
|
72
|
+
<tt>java -jar myrailsapp.war -S rake db:migrate</tt>.
|
73
|
+
* +executable+: This bundles an embedded web server into the .war so that it
|
74
|
+
can either be deployed into a traditional java web server or run as a
|
75
|
+
standalone application using <tt>java -jar myapp.war</tt>.
|
76
|
+
(Note: jar-based applications are executable by default.)
|
77
|
+
* +compiled+: This uses +jrubyc+ to precompile all .rb files in your application
|
78
|
+
to .class files and includes those in the .war instead of the Ruby sources.
|
79
|
+
NOTE: The war file will still contain .rb files, but they will be short stubs
|
80
|
+
containing the following code : <tt>load __FILE__.sub(/\.rb$/, '.class')</tt>
|
81
|
+
|
82
|
+
Features may form the basis for a third-party plugin system (in the future)
|
83
|
+
if there is demand.
|
84
|
+
|
85
|
+
NOTE: Feature tasks must be included in the same command invocation and
|
86
|
+
inserted before the +war+ task in order to take effect. For example,
|
87
|
+
<tt>warble compiled; warble war</tt> does not compile and obfuscate +.rb+
|
88
|
+
sources because the second invocation of +warble+ does not run the +compiled+
|
89
|
+
feature and creates a basic war with the sources included, make sure you run :
|
90
|
+
|
91
|
+
warble compiled war
|
92
|
+
|
93
|
+
or, if it's important that the war always be compiled, use the option above to
|
94
|
+
put the feature in your <tt>config/warble.rb</tt>.
|
95
|
+
|
96
|
+
== .war or .jar?
|
97
|
+
|
98
|
+
War-based projects are for Rails, Merb, or Rack-based web applications.
|
99
|
+
They usually contain a +config/environment.rb+ file, a +config/init.rb+ file,
|
100
|
+
or a +config.ru+ file.
|
101
|
+
The presence of these files are used to determine if the project is a web
|
102
|
+
application, and thus a Java EE compatible war file is built for the project.
|
103
|
+
|
104
|
+
Jar-based projects are for standalone Ruby applications. Usually a Ruby
|
105
|
+
application has a launcher script in the +bin+ directory and Ruby code
|
106
|
+
in the <tt>lib</tt> directory. Warbler packages the application so that
|
107
|
+
<tt>java -jar myapp.jar</tt> runs the launcher script.
|
108
|
+
|
109
|
+
== Jar Files
|
110
|
+
|
111
|
+
=== Gem Specification Files
|
112
|
+
|
113
|
+
If your project has a <tt>.gemspec</tt> file in the top directory, it will be
|
114
|
+
used to configure the project's dependencies, launcher script, require paths,
|
115
|
+
and the files to be included in the archive. For best results make sure your
|
116
|
+
gemspec specifies all of the following attributes:
|
117
|
+
|
118
|
+
* +executables+
|
119
|
+
* +require_paths+
|
120
|
+
* runtime dependencies added with +add_dependency+
|
121
|
+
|
122
|
+
If your project do not have a <tt>.gemspec</tt>, Warbler will attempt to guess
|
123
|
+
the launcher from the contents of the <tt>bin</tt> directory and use the
|
124
|
+
<tt>lib</tt> directory as the lone require path. All files in the project
|
125
|
+
will be included in the archive.
|
126
|
+
|
127
|
+
=== Bundler
|
128
|
+
|
129
|
+
Applications that use Bundler[http://gembundler.com/], detected via presence of
|
130
|
+
a +Gemfile+, will have the gems packaged up into the archive along with the
|
131
|
+
Gemfile. The Bundler groups named ":development", ":test" and ":assets" will be
|
132
|
+
excluded by default, unless you specify with <tt>config.bundle_without</tt> in
|
133
|
+
+config/warble.rb+.
|
134
|
+
|
135
|
+
Warbler supports Bundler for gems and git repositories, but not for plain path
|
136
|
+
components. Warbler will warn when a +:path+ component is found in the +Gemfile+
|
137
|
+
and will refuse to include it in the archive.
|
138
|
+
|
139
|
+
=== JBundler (experimental)
|
140
|
+
|
141
|
+
Applications that use JBundler[http://github.com/mkristian/jbundler], detected
|
142
|
+
via presence of a +Jarfile+, will have the jars packaged up into the archive. the JBundler gem is **not** needed for runtime since all jars are already part of the classloader.
|
143
|
+
|
144
|
+
== War Files
|
145
|
+
|
146
|
+
=== Rails applications
|
147
|
+
|
148
|
+
Rails applications are detected automatically and configured appropriately.
|
149
|
+
The following items are set up for you:
|
150
|
+
|
151
|
+
* Your application runs in the +production+ environment by default.
|
152
|
+
Change it in +config/warble.rb+ (see below).
|
153
|
+
* The Rails gem is packaged if you haven't vendored Rails (Rails <= 2.x).
|
154
|
+
* Other gems configured in Rails.configuration.gems are packaged (2.1 - 2.3)
|
155
|
+
* Multi-thread-safe execution (as introduced in Rails 2.2) is detected and
|
156
|
+
runtime pooling is disabled.
|
157
|
+
|
158
|
+
=== Other Rack Applications
|
159
|
+
|
160
|
+
If you have a +config.ru+ file in the top directory or one of the immediate
|
161
|
+
subdirectories of your application, it will be included and used as the rackup
|
162
|
+
script for your Rack-based application. You will probably need to specify
|
163
|
+
framework and application gems in +config/warble.rb+ unless you're using Bundler
|
164
|
+
to manage your gems. <tt>ENV['RACK_ENV']</tt> will be set to +production+.
|
165
|
+
|
166
|
+
See {the examples in the jruby-rack project}[http://github.com/jruby/jruby-rack/tree/master/examples/]
|
167
|
+
of how to configure Warbler to package Camping and Sinatra apps.
|
168
|
+
|
169
|
+
=== Configuration Notes
|
170
|
+
|
171
|
+
* Warbler will load the +environment+ Rake task in a Rails application to try
|
172
|
+
to detect some configuration. If you don't have database access in the
|
173
|
+
environment where you package your application, you may wish to set
|
174
|
+
<tt>Warbler.framework_detection = false</tt> at the top of +config.rb+.
|
175
|
+
In this case you may need to specify additional details such as booter, gems
|
176
|
+
and other settings that would normally be gleaned from the app configuration.
|
177
|
+
* Is it possible to more generally detect what gems an application uses?
|
178
|
+
<tt>Gem.loaded_specs</tt> is available, but the application needs to be
|
179
|
+
loaded first before its contents are reliable.
|
180
|
+
|
181
|
+
== Custom Configuration
|
182
|
+
|
183
|
+
If the default settings are not appropriate for your application, you can
|
184
|
+
customize Warbler's behavior. To customize files, libraries, and gems included
|
185
|
+
in the .war file, you'll need a config/warble.rb file. There a two ways of
|
186
|
+
doing this. With the gem, simply run
|
187
|
+
|
188
|
+
warble config
|
189
|
+
|
190
|
+
Finally, edit the +config/warble.rb+ to your taste. The generated file is
|
191
|
+
fully-documented with the available options and default values.
|
192
|
+
|
193
|
+
=== Archive Layout
|
194
|
+
|
195
|
+
The default configuration puts application files (+app+, +config+, +lib+,
|
196
|
+
+log+, +vendor+, +tmp+) under the .war file's +WEB-INF+ directory, and files in
|
197
|
+
+public+ in the root of the .war file. Any Java .jar files stored in lib will
|
198
|
+
automatically be placed in +WEB-INF/lib+ for placement on the web app's
|
199
|
+
class-path.
|
200
|
+
|
201
|
+
=== web.xml
|
202
|
+
|
203
|
+
Java web applications are configured mainly through this file, and Warbler
|
204
|
+
creates a suitable default file for you for use. However, if you need to
|
205
|
+
customize it in any way, you have two options.
|
206
|
+
|
207
|
+
1. If you just want a static web.xml file whose contents you manually
|
208
|
+
control, you may unzip the one generated for you in
|
209
|
+
<tt>yourapp.war:WEB-INF/web.xml</tt> to <tt>config/web.xml</tt> and
|
210
|
+
modify as needed. It will be copied into subsequent copies of the
|
211
|
+
war file for you.
|
212
|
+
2. If you want to inject some dynamic information into the file, copy
|
213
|
+
the <tt>WARBLER_HOME/web.xml.erb</tt> to
|
214
|
+
<tt>config/web.xml.erb</tt>. Its contents will be evaluated for you
|
215
|
+
and put in the webapp. Note that you can also pass arbitrary
|
216
|
+
properties to the ERb template by setting
|
217
|
+
<tt>config.webxml.customkey</tt> values in your
|
218
|
+
<tt>config/warble.rb</tt> file.
|
219
|
+
|
220
|
+
For more information on configuration, see Warbler::Config.
|
221
|
+
|
222
|
+
== Rakefile Integration
|
223
|
+
|
224
|
+
If you'd like to control Warbler from your own project's +Rakefile+,
|
225
|
+
simply add the following code somewhere in the +Rakefile+ :
|
226
|
+
|
227
|
+
require 'warbler'
|
228
|
+
Warbler::Task.new
|
229
|
+
|
230
|
+
If you're using Bundler, you'll want to add Warbler to your +Gemfile+ :
|
231
|
+
|
232
|
+
group :development do
|
233
|
+
gem "warbler", :require => false
|
234
|
+
end
|
235
|
+
|
236
|
+
Now you should be able to invoke <tt>rake war</tt> to create your war file.
|
237
|
+
|
238
|
+
== Troubleshooting
|
239
|
+
|
240
|
+
If Warbler isn't packaging the files you were expecting, use the +war:debug+
|
241
|
+
task to give you more insight into what's going on.
|
242
|
+
|
243
|
+
If you think you found a bug, please file one at
|
244
|
+
https://github.com/jruby/warbler/issues.
|
245
|
+
|
246
|
+
== Source
|
247
|
+
|
248
|
+
You can get the Warbler source using Git, in any of the following ways:
|
249
|
+
|
250
|
+
git clone git://github.com/jruby/warbler.git
|
251
|
+
|
252
|
+
You can also download a tarball of Warbler source at
|
253
|
+
https://github.com/jruby/warbler/archive/master.zip.
|
254
|
+
|
255
|
+
== Development
|
256
|
+
|
257
|
+
You can develop Warbler with any implementation of Ruby. To write Warbler code
|
258
|
+
and run specs, you need to have Bundler installed and run <tt>bundle</tt> once.
|
259
|
+
|
260
|
+
After that, simply run <tt>rake</tt>.
|
261
|
+
|
262
|
+
=== Integration Tests
|
263
|
+
|
264
|
+
There are a few integration tests in the `integration` directory that build WAR file
|
265
|
+
with Warbler, and run some basic smoke tests against them. You can run these like so:
|
266
|
+
|
267
|
+
cd integration
|
268
|
+
mvn verify
|
269
|
+
|
270
|
+
You'll need to have Maven >= 3.1.1 installed, of course: http://maven.apache.org/
|
271
|
+
|
272
|
+
== License
|
273
|
+
|
274
|
+
Warbler is provided under the terms of the MIT license.
|
275
|
+
|
276
|
+
Warbler (c) 2013-2018 The JRuby Team
|
277
|
+
|
278
|
+
Warbler (c) 2010-2012 Engine Yard, Inc.
|
279
|
+
|
280
|
+
Warbler (c) 2007-2009 Sun Microsystems, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#-*- mode: ruby -*-
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2010-2012 Engine Yard, Inc.
|
4
|
+
# Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
5
|
+
# This source code is available under the MIT license.
|
6
|
+
# See the file LICENSE.txt for details.
|
7
|
+
#++
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'bundler'
|
11
|
+
rescue LoadError
|
12
|
+
warn "\nPlease `gem install bundler' and run `bundle install' to ensure you have all dependencies.\n\n"
|
13
|
+
else
|
14
|
+
require 'bundler/gem_helper'
|
15
|
+
Bundler::GemHelper.install_tasks :dir => File.dirname(__FILE__)
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'rake/clean'
|
19
|
+
CLEAN << "pkg" << "doc" << Dir['integration/**/target']
|
20
|
+
|
21
|
+
require 'rspec/core/rake_task'
|
22
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
23
|
+
t.rspec_opts = ['--color', "--format documentation"]
|
24
|
+
end
|
25
|
+
|
26
|
+
task :default => :spec
|
27
|
+
|
28
|
+
# use Mavenfile to define :jar task
|
29
|
+
require 'maven/ruby/maven'
|
30
|
+
mvn = Maven::Ruby::Maven.new
|
31
|
+
if defined?(JRUBY_VERSION) && !JRUBY_VERSION.start_with?('9.0')
|
32
|
+
mvn.inherit_jruby_version
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'compile java sources and build jar'
|
36
|
+
task :jar do
|
37
|
+
mvn.prepare_package
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'run some integration test'
|
41
|
+
task :integration do
|
42
|
+
mvn.verify
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'generate the pom.xml from the Mavenfile'
|
46
|
+
task :pom do
|
47
|
+
mvn.validate('-Dpolyglot.dump.pom=pom.xml')
|
48
|
+
end
|
49
|
+
|
50
|
+
# Make sure jar gets compiled before the gem is built
|
51
|
+
task :build => :jar
|
52
|
+
|
53
|
+
require 'rdoc/task'
|
54
|
+
RDoc::Task.new(:docs) do |rd|
|
55
|
+
gemspec = Gem::Specification.load(File.expand_path('warbler.gemspec', File.dirname(__FILE__)))
|
56
|
+
rd.rdoc_dir = "doc"
|
57
|
+
rd.rdoc_files.include("README.rdoc", "History.txt", "LICENSE.txt")
|
58
|
+
rd.rdoc_files += gemspec.require_paths
|
59
|
+
rd.options << '--title' << "#{gemspec.name}-#{gemspec.version} Documentation"
|
60
|
+
rd.options += gemspec.rdoc_options
|
61
|
+
end
|
62
|
+
|
63
|
+
task :release => :docs
|
data/bin/warble
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2010-2011 Engine Yard, Inc.
|
5
|
+
# Copyright (c) 2007-2009 Sun Microsystems, Inc.
|
6
|
+
# This source code is available under the MIT license.
|
7
|
+
# See the file LICENSE.txt for details.
|
8
|
+
#++
|
9
|
+
|
10
|
+
require 'warbler'
|
11
|
+
Warbler::Application.run
|
data/ext/JarMain.java
ADDED
@@ -0,0 +1,334 @@
|
|
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.File;
|
9
|
+
import java.io.IOException;
|
10
|
+
import java.io.FileOutputStream;
|
11
|
+
import java.io.InputStream;
|
12
|
+
import java.io.PrintStream;
|
13
|
+
import java.lang.reflect.InvocationTargetException;
|
14
|
+
import java.lang.reflect.Method;
|
15
|
+
import java.net.URI;
|
16
|
+
import java.net.URISyntaxException;
|
17
|
+
import java.net.URL;
|
18
|
+
import java.net.URLClassLoader;
|
19
|
+
import java.util.ArrayList;
|
20
|
+
import java.util.Arrays;
|
21
|
+
import java.util.Enumeration;
|
22
|
+
import java.util.HashMap;
|
23
|
+
import java.util.List;
|
24
|
+
import java.util.Map;
|
25
|
+
import java.util.jar.JarEntry;
|
26
|
+
import java.util.jar.JarFile;
|
27
|
+
|
28
|
+
public class JarMain implements Runnable {
|
29
|
+
|
30
|
+
static final String MAIN = '/' + JarMain.class.getName().replace('.', '/') + ".class";
|
31
|
+
|
32
|
+
protected final String[] args;
|
33
|
+
protected final String archive;
|
34
|
+
private final String path;
|
35
|
+
|
36
|
+
protected File extractRoot;
|
37
|
+
|
38
|
+
protected URLClassLoader classLoader;
|
39
|
+
|
40
|
+
JarMain(String[] args) {
|
41
|
+
this.args = args;
|
42
|
+
URL mainClass = getClass().getResource(MAIN);
|
43
|
+
URI uri;
|
44
|
+
File file;
|
45
|
+
|
46
|
+
try {
|
47
|
+
this.path = mainClass.toURI().getSchemeSpecificPart();
|
48
|
+
uri = new URI(this.path.replace("!" + MAIN, ""));
|
49
|
+
}
|
50
|
+
catch (URISyntaxException e) {
|
51
|
+
throw new RuntimeException(e);
|
52
|
+
}
|
53
|
+
|
54
|
+
archive = new File(uri.getPath()).getAbsolutePath();
|
55
|
+
|
56
|
+
Runtime.getRuntime().addShutdownHook(new Thread(this));
|
57
|
+
}
|
58
|
+
|
59
|
+
protected URL[] extractArchive() throws Exception {
|
60
|
+
final JarFile jarFile = new JarFile(archive);
|
61
|
+
try {
|
62
|
+
Map<String, JarEntry> jarNames = new HashMap<String, JarEntry>();
|
63
|
+
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
|
64
|
+
JarEntry entry = e.nextElement();
|
65
|
+
String extractPath = getExtractEntryPath(entry);
|
66
|
+
if ( extractPath != null ) jarNames.put(extractPath, entry);
|
67
|
+
}
|
68
|
+
|
69
|
+
extractRoot = File.createTempFile("jruby", "extract");
|
70
|
+
extractRoot.delete(); extractRoot.mkdirs();
|
71
|
+
|
72
|
+
final List<URL> urls = new ArrayList<URL>(jarNames.size());
|
73
|
+
for (Map.Entry<String, JarEntry> e : jarNames.entrySet()) {
|
74
|
+
URL entryURL = extractEntry(e.getValue(), e.getKey());
|
75
|
+
if (entryURL != null) urls.add( entryURL );
|
76
|
+
}
|
77
|
+
return urls.toArray(new URL[urls.size()]);
|
78
|
+
}
|
79
|
+
finally {
|
80
|
+
jarFile.close();
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
protected String getExtractEntryPath(final JarEntry entry) {
|
85
|
+
final String name = entry.getName();
|
86
|
+
if ( name.startsWith("META-INF/lib") && name.endsWith(".jar") ) {
|
87
|
+
return name.substring(name.lastIndexOf('/') + 1);
|
88
|
+
}
|
89
|
+
return null; // do not extract entry
|
90
|
+
}
|
91
|
+
|
92
|
+
protected URL extractEntry(final JarEntry entry, final String path) throws Exception {
|
93
|
+
final File file = new File(extractRoot, path);
|
94
|
+
if ( entry.isDirectory() ) {
|
95
|
+
file.mkdirs();
|
96
|
+
return null;
|
97
|
+
}
|
98
|
+
final String entryPath = entryPath(entry.getName());
|
99
|
+
final InputStream entryStream;
|
100
|
+
try {
|
101
|
+
entryStream = new URI("jar", entryPath, null).toURL().openStream();
|
102
|
+
}
|
103
|
+
catch (IllegalArgumentException e) {
|
104
|
+
// TODO gems '%' file name "encoding" ?!
|
105
|
+
debug("failed to open jar:" + entryPath + " skipping entry: " + entry.getName(), e);
|
106
|
+
return null;
|
107
|
+
}
|
108
|
+
final File parent = file.getParentFile();
|
109
|
+
if ( parent != null ) parent.mkdirs();
|
110
|
+
FileOutputStream outStream = new FileOutputStream(file);
|
111
|
+
final byte[] buf = new byte[65536];
|
112
|
+
try {
|
113
|
+
int bytesRead;
|
114
|
+
while ((bytesRead = entryStream.read(buf)) != -1) {
|
115
|
+
outStream.write(buf, 0, bytesRead);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
finally {
|
119
|
+
entryStream.close();
|
120
|
+
outStream.close();
|
121
|
+
file.deleteOnExit();
|
122
|
+
}
|
123
|
+
// if (false) debug(entry.getName() + " extracted to " + file.getPath());
|
124
|
+
return file.toURI().toURL();
|
125
|
+
}
|
126
|
+
|
127
|
+
protected String entryPath(String name) {
|
128
|
+
if ( ! name.startsWith("/") ) name = "/" + name;
|
129
|
+
return path.replace(MAIN, name);
|
130
|
+
}
|
131
|
+
|
132
|
+
protected Object newScriptingContainer(final URL[] jars) throws Exception {
|
133
|
+
setSystemProperty("org.jruby.embed.class.path", "");
|
134
|
+
classLoader = new URLClassLoader(jars);
|
135
|
+
Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, classLoader);
|
136
|
+
Object scriptingContainer = scriptingContainerClass.newInstance();
|
137
|
+
debug("scripting container class loader urls: " + Arrays.toString(jars));
|
138
|
+
invokeMethod(scriptingContainer, "setArgv", (Object) args);
|
139
|
+
invokeMethod(scriptingContainer, "setClassLoader", new Class[] { ClassLoader.class }, classLoader);
|
140
|
+
return scriptingContainer;
|
141
|
+
}
|
142
|
+
|
143
|
+
protected int launchJRuby(final URL[] jars) throws Exception {
|
144
|
+
final Object scriptingContainer = newScriptingContainer(jars);
|
145
|
+
debug("invoking " + archive + " with: " + Arrays.deepToString(args));
|
146
|
+
Object outcome = invokeMethod(scriptingContainer, "runScriptlet", launchScript());
|
147
|
+
return ( outcome instanceof Number ) ? ( (Number) outcome ).intValue() : 0;
|
148
|
+
}
|
149
|
+
|
150
|
+
protected String launchScript() {
|
151
|
+
return
|
152
|
+
"begin\n" +
|
153
|
+
" require 'META-INF/init.rb'\n" +
|
154
|
+
" require 'META-INF/main.rb'\n" +
|
155
|
+
" 0\n" +
|
156
|
+
"rescue SystemExit => e\n" +
|
157
|
+
" e.status\n" +
|
158
|
+
"end";
|
159
|
+
}
|
160
|
+
|
161
|
+
protected int start() throws Exception {
|
162
|
+
final URL[] jars = extractArchive();
|
163
|
+
return launchJRuby(jars);
|
164
|
+
}
|
165
|
+
|
166
|
+
protected void debug(String msg) {
|
167
|
+
debug(msg, null);
|
168
|
+
}
|
169
|
+
|
170
|
+
protected void debug(String msg, Throwable t) {
|
171
|
+
if ( isDebug() ) System.out.println(msg);
|
172
|
+
if ( isDebug() && t != null ) t.printStackTrace(System.out);
|
173
|
+
}
|
174
|
+
|
175
|
+
protected static void debug(Throwable t) {
|
176
|
+
debug(t, System.out);
|
177
|
+
}
|
178
|
+
|
179
|
+
private static void debug(Throwable t, PrintStream out) {
|
180
|
+
if ( isDebug() ) t.printStackTrace(out);
|
181
|
+
}
|
182
|
+
|
183
|
+
protected void warn(String msg) {
|
184
|
+
System.out.println("WARNING: " + msg);
|
185
|
+
}
|
186
|
+
|
187
|
+
protected static void error(Throwable t) {
|
188
|
+
error(t.toString(), t);
|
189
|
+
}
|
190
|
+
|
191
|
+
protected static void error(String msg, Throwable t) {
|
192
|
+
System.err.println("ERROR: " + msg);
|
193
|
+
debug(t, System.err);
|
194
|
+
}
|
195
|
+
|
196
|
+
protected void delete(File f) {
|
197
|
+
try {
|
198
|
+
if (f.isDirectory() && !isSymlink(f)) {
|
199
|
+
File[] children = f.listFiles();
|
200
|
+
for (int i = 0; i < children.length; i++) {
|
201
|
+
delete(children[i]);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
f.delete();
|
205
|
+
}
|
206
|
+
catch (IOException e) { error(e); }
|
207
|
+
}
|
208
|
+
|
209
|
+
protected boolean isSymlink(File file) throws IOException {
|
210
|
+
if (file == null) throw new NullPointerException("File must not be null");
|
211
|
+
final File canonical;
|
212
|
+
if ( file.getParent() == null ) canonical = file;
|
213
|
+
else {
|
214
|
+
File parentDir = file.getParentFile().getCanonicalFile();
|
215
|
+
canonical = new File(parentDir, file.getName());
|
216
|
+
}
|
217
|
+
return ! canonical.getCanonicalFile().equals( canonical.getAbsoluteFile() );
|
218
|
+
}
|
219
|
+
|
220
|
+
public void run() {
|
221
|
+
// If the URLClassLoader isn't closed, on Windows, temp JARs won't be cleaned up
|
222
|
+
try {
|
223
|
+
invokeMethod(classLoader, "close");
|
224
|
+
}
|
225
|
+
catch (NoSuchMethodException e) { } // We're not being run on Java >= 7
|
226
|
+
catch (Exception e) { error(e); }
|
227
|
+
|
228
|
+
if ( extractRoot != null ) delete(extractRoot);
|
229
|
+
}
|
230
|
+
|
231
|
+
public static void main(String[] args) {
|
232
|
+
doStart(new JarMain(args));
|
233
|
+
}
|
234
|
+
|
235
|
+
protected static void doStart(final JarMain main) {
|
236
|
+
int exit;
|
237
|
+
try {
|
238
|
+
exit = main.start();
|
239
|
+
}
|
240
|
+
catch (Exception e) {
|
241
|
+
Throwable t = e;
|
242
|
+
while ( t.getCause() != null && t.getCause() != t ) {
|
243
|
+
t = t.getCause();
|
244
|
+
}
|
245
|
+
error(e.toString(), t);
|
246
|
+
exit = 1;
|
247
|
+
}
|
248
|
+
try {
|
249
|
+
if ( isSystemExitEnabled() ) System.exit(exit);
|
250
|
+
}
|
251
|
+
catch (SecurityException e) {
|
252
|
+
debug(e);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
protected static Object invokeMethod(final Object self, final String name, final Object... args)
|
257
|
+
throws NoSuchMethodException, IllegalAccessException, Exception {
|
258
|
+
|
259
|
+
final Class[] signature = new Class[args.length];
|
260
|
+
for ( int i = 0; i < args.length; i++ ) signature[i] = args[i].getClass();
|
261
|
+
return invokeMethod(self, name, signature, args);
|
262
|
+
}
|
263
|
+
|
264
|
+
protected static Object invokeMethod(final Object self, final String name, final Class[] signature, final Object... args)
|
265
|
+
throws NoSuchMethodException, IllegalAccessException, Exception {
|
266
|
+
Method method = self.getClass().getDeclaredMethod(name, signature);
|
267
|
+
try {
|
268
|
+
return method.invoke(self, args);
|
269
|
+
}
|
270
|
+
catch (InvocationTargetException e) {
|
271
|
+
Throwable target = e.getTargetException();
|
272
|
+
if (target instanceof Exception) {
|
273
|
+
throw (Exception) target;
|
274
|
+
}
|
275
|
+
throw e;
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
private static final boolean debug;
|
280
|
+
static {
|
281
|
+
debug = Boolean.parseBoolean( getSystemProperty("warbler.debug", "false") );
|
282
|
+
}
|
283
|
+
|
284
|
+
static boolean isDebug() { return debug; }
|
285
|
+
|
286
|
+
/**
|
287
|
+
* if warbler.skip_system_exit system property is defined, we will not
|
288
|
+
* call System.exit in the normal flow. System.exit can cause problems
|
289
|
+
* for wrappers like procrun
|
290
|
+
*/
|
291
|
+
private static boolean isSystemExitEnabled(){
|
292
|
+
return getSystemProperty("warbler.skip_system_exit") == null; //omission enables System.exit use
|
293
|
+
}
|
294
|
+
|
295
|
+
static String getSystemProperty(final String name) {
|
296
|
+
return getSystemProperty(name, null);
|
297
|
+
}
|
298
|
+
|
299
|
+
static String getSystemProperty(final String name, final String defaultValue) {
|
300
|
+
try {
|
301
|
+
return System.getProperty(name, defaultValue);
|
302
|
+
}
|
303
|
+
catch (SecurityException e) {
|
304
|
+
return defaultValue;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
static boolean setSystemProperty(final String name, final String value) {
|
309
|
+
try {
|
310
|
+
System.setProperty(name, value);
|
311
|
+
return true;
|
312
|
+
}
|
313
|
+
catch (SecurityException e) {
|
314
|
+
return false;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
static String getENV(final String name) {
|
319
|
+
return getENV(name, null);
|
320
|
+
}
|
321
|
+
|
322
|
+
static String getENV(final String name, final String defaultValue) {
|
323
|
+
try {
|
324
|
+
if ( System.getenv().containsKey(name) ) {
|
325
|
+
return System.getenv().get(name);
|
326
|
+
}
|
327
|
+
return defaultValue;
|
328
|
+
}
|
329
|
+
catch (SecurityException e) {
|
330
|
+
return defaultValue;
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
}
|