warbler_updated 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|