jarbler 0.3.5 → 0.4.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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +17 -13
- data/lib/jarbler/JarMain.java +50 -19
- data/lib/jarbler/builder.rb +6 -6
- data/lib/jarbler/config.rb +46 -25
- data/lib/jarbler/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 792e9125f2dd1d6a61bf741655f724f6139777ae2ef8f7076874520024764d3d
|
4
|
+
data.tar.gz: e5d238d29e05a075a7d60ecdfda2b239b27c8b873aa917f029ee92c45cfcb50e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 441fff46742aa485e9e2adfbf8f2e09478b835c450c735d4060bfe302bb3bf0cb27c50cc976e4e9893d20089921797706e192a7d30681f091b46183d646c3229
|
7
|
+
data.tar.gz: d9f8addc2d6b81fd20692956524c325fa438d0dd1eac257cffb5ae7b659724698b4e8b1218169119cd5943c48d0fa12d498126cba785c9b632060c37d7599995
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
|
4
|
+
## [0.4.0] - 2025-04-24
|
5
|
+
|
6
|
+
- config attribute `compile_java_version` removed and replaced by 'java_opts'
|
7
|
+
- new config attribute `java_opts` allows to set additional options for the Java compiler used for jar file bootstrap code
|
8
|
+
- `java_opts` does not affect the optional AOT compilation of Ruby files.
|
9
|
+
- new config attribute `jrubyc_opts` allows to set additional options for JRuby's AOT compiler used for compilation of Ruby files
|
10
|
+
|
11
|
+
## [0.3.6] - 2025-03-31
|
12
|
+
|
13
|
+
- remove temporary folder with extracted jar content after termination of Ruby code even if Ruby code terminates the JVM hard with 'exit' or 'System.exit'
|
14
|
+
- provide exit code of Ruby code as exit code of the jar file execution
|
15
|
+
|
3
16
|
## [0.3.5] - 2025-03-22
|
4
17
|
|
5
18
|
- new config attribute "config.compile_java_version" allows control of setting for "javac -source and -target" for AOT compilation
|
data/README.md
CHANGED
@@ -25,7 +25,7 @@ To create a jar file simply run "jarble" in your application's root directory.
|
|
25
25
|
|
26
26
|
$ jarble
|
27
27
|
|
28
|
-
To adjust Jarbler's configuration, modify the settings in config file
|
28
|
+
To adjust Jarbler's configuration, modify the settings in config file `config/jarble.rb`. The template for this config file you create by executing
|
29
29
|
|
30
30
|
$ jarble config
|
31
31
|
|
@@ -45,7 +45,7 @@ Additional command line parameters are passed through to the executed Ruby app (
|
|
45
45
|
|
46
46
|
## Configuration
|
47
47
|
|
48
|
-
The file config/jarble.rb contains the configuration for Jarbler.
|
48
|
+
The file `config/jarble.rb` contains the configuration for Jarbler.
|
49
49
|
To create a template config file with information about all the supported configuration options, execute:
|
50
50
|
|
51
51
|
$ jarble config
|
@@ -53,17 +53,18 @@ To create a template config file with information about all the supported config
|
|
53
53
|
The default configuration is focused on Ruby on Rails applications.<br>
|
54
54
|
|
55
55
|
### Configuration options
|
56
|
-
| Option
|
57
|
-
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
| Option | Default value | Description |
|
57
|
+
|-----------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
58
|
+
| compile_ruby_files | false | Ahead of time compilation of all .rb files of the application to .class files.<br/>Only the .class files are stored in the jar file. The Gem dependencies are not compiled. Requires JRuby as Ruby environment at compile time. |
|
59
|
+
| executable | "bin/rails" | The ruby start file to run at execution of jar file. File extension .class is used automatically if start file is .rb and AOT compilation is used. |
|
60
|
+
| executable_params | ["server", "-e", "production", "-p", "8080"] | Command line parameters to be used for the ruby executable |
|
61
|
+
| excludes_from_compile | [] | The files and dirs of the project to exclude from the compilation of .rb files. Paths specifies the location in the jar file (e.g. ["app_root/file.rb"] ) |
|
62
|
+
| excludes | ["tmp/cache", "tmp/pids", ...] (see generated template file for whole content) | The files and dirs of the project to exclude from the include option |
|
63
|
+
| includes | ["app", "bin", "config", ...] (see generated template file for whole content) | The files and dirs of the project to include in the jar file |
|
64
|
+
| jar_name | < Name of project dir >.jar | The name of the generated jar file |
|
65
|
+
| java_opts | none | Additional options for the Java compiler (javac).<br/>Used for the compilation of the jar file bootstrap class JarMain.java.<br/>Does not influence the optional AOT compilation of the application's ruby files.<br/>E.g. control the class file version of the JarMain.class with `java_opts = '-target 1.8 -source 1.8‘`. |
|
66
|
+
| jrubyc_opts | [] | Additional options for the JRuby compiler (jrubyc).<br/>Used for the AOT compilation of the application's ruby files if `compile_ruby_files:true`.<br/><br/>Some of the possible values are:<ul><li>`-J<java opts>`options for javac if `--java` or `--javac` is also set in `jrubyc_opts`, otherwise ignored</li><li>`--java` Generate Java classes (.java) for a script containing Ruby class definitions</li><li>`--javac` Generate Java classes (.java and .class) for a script containing Ruby class definitions</li><li>`-c or --classpath PATH` Add a jar to the classpath for building</li><li>`--verbose` Log verbose output while compile</li></ul> |
|
67
|
+
| jruby_version | A valid JRuby version from file '.ruby-version' or the current most recent version of the Gem 'jruby-jars' | The version of the JRuby runtime to use |
|
67
68
|
|
68
69
|
|
69
70
|
## Troubleshooting
|
@@ -74,6 +75,9 @@ The default configuration is focused on Ruby on Rails applications.<br>
|
|
74
75
|
* Gem::LoadError: You have already activated ..., but your Gemfile requires ... . Since ... is a default gem, you can either remove your dependency on it or try updating to a newer version of bundler that supports net-protocol as a default gem.
|
75
76
|
* Reason: Mismatch between the version of the local requested gem and the version of the default gem
|
76
77
|
* Solution: Update the default gems to the requested version
|
78
|
+
* TypeError:no implicit conversion of Symbol into Integer at lib/ruby/stdlib/jruby/compiler.rb:61
|
79
|
+
* Reason: There was an error in JRuby compiler upto 9.4.12.0 or 10.0.0.0. if combining `compile_ruby_files' with `java_opts`. See: https://github.com/jruby/jruby/issues/8795
|
80
|
+
* Solution: Avoid using this combination or use a newer JRuby version where this issue is fixed.
|
77
81
|
|
78
82
|
|
79
83
|
## Contributing
|
data/lib/jarbler/JarMain.java
CHANGED
@@ -33,6 +33,9 @@ import java.security.ProtectionDomain;
|
|
33
33
|
|
34
34
|
class JarMain {
|
35
35
|
|
36
|
+
// declare as class variable to be used in addShutdownHook
|
37
|
+
private static URLClassLoader classLoader = null;
|
38
|
+
|
36
39
|
// executed by java -jar <jar file name>
|
37
40
|
public static void main(String[] args) {
|
38
41
|
debug("Start java process in jar file "+jar_file_name());
|
@@ -113,7 +116,7 @@ class JarMain {
|
|
113
116
|
create_bundle_config(app_root, gem_home);
|
114
117
|
|
115
118
|
// Load the Jar file
|
116
|
-
|
119
|
+
classLoader = new URLClassLoader(new URL[]{
|
117
120
|
jrubyCoreFile.toURI().toURL(),
|
118
121
|
jrubyStdlibFile.toURI().toURL()
|
119
122
|
//new URL("file:/" + jrubyCoreFile.getAbsolutePath()),
|
@@ -159,19 +162,39 @@ class JarMain {
|
|
159
162
|
debug(" - " + arg);
|
160
163
|
}
|
161
164
|
|
165
|
+
// Add code to execute at System.exit
|
166
|
+
// ensure cleanup of the temporary directory also at hard exit in Ruby code like 'exit' or 'System.exit'
|
167
|
+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
168
|
+
debug("Execute shutdown hook");
|
169
|
+
try {
|
170
|
+
if (classLoader != null) {
|
171
|
+
// Free the JRuby jars to allow deletion of the temporary directory
|
172
|
+
classLoader.close();
|
173
|
+
classLoader = null; // Remove reference
|
174
|
+
System.gc(); // Suggest garbage collection
|
175
|
+
}
|
176
|
+
// remove the temp directory newFolder if not DEBUG mode
|
177
|
+
if (debug_active()) {
|
178
|
+
System.out.println("DEBUG mode is active, temporary folder is not removed at process termination: "+ newFolder.getAbsolutePath());
|
179
|
+
} else {
|
180
|
+
deleteFolder(newFolder);
|
181
|
+
}
|
182
|
+
} catch (Exception e) {
|
183
|
+
System.err.println("Exception in shutdown hook: "+ e.getMessage());
|
184
|
+
e.printStackTrace();
|
185
|
+
}
|
186
|
+
}));
|
187
|
+
|
162
188
|
// call the method org.jruby.Main.main
|
163
189
|
debug("Calling org.jruby.Main.main with: "+ mainArgs);
|
164
190
|
mainMethod.invoke(null, (Object)mainArgs.toArray(new String[mainArgs.size()]));
|
165
|
-
// TODO: evaluate return value
|
166
191
|
} catch (Exception e) {
|
167
192
|
e.printStackTrace();
|
193
|
+
System.exit(1); // signal unsuccessful termination
|
168
194
|
} finally {
|
169
|
-
//
|
170
|
-
if
|
171
|
-
|
172
|
-
} else {
|
173
|
-
deleteFolder(newFolder);
|
174
|
-
}
|
195
|
+
// Called only if the JVM is not terminated by System.exit before, see addShutdownHook
|
196
|
+
// This code is not executed if called 'exit' or 'System.exit' in Ruby code before
|
197
|
+
debug("Applicaton finished in finalize block");
|
175
198
|
}
|
176
199
|
}
|
177
200
|
|
@@ -240,24 +263,32 @@ class JarMain {
|
|
240
263
|
|
241
264
|
}
|
242
265
|
|
266
|
+
private static boolean debug_active() {
|
267
|
+
String debug = System.getenv("DEBUG");
|
268
|
+
return debug != null && debug.toUpperCase().equals("TRUE");
|
269
|
+
}
|
270
|
+
|
243
271
|
private static void debug(String msg) {
|
244
|
-
if (
|
272
|
+
if (debug_active()) {
|
245
273
|
System.err.println(msg);
|
246
274
|
}
|
247
275
|
}
|
248
276
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
277
|
+
private static void deleteFolder(File file) {
|
278
|
+
try
|
279
|
+
{
|
280
|
+
if (file.isDirectory()) {
|
281
|
+
File[] entries = file.listFiles();
|
282
|
+
for (File currentFile: entries) {
|
283
|
+
deleteFolder(currentFile);
|
284
|
+
}
|
285
|
+
}
|
286
|
+
file.delete();
|
287
|
+
} catch(Throwable t) {
|
288
|
+
System.err.println("Could not DELETE file: " + file.getAbsolutePath() + " - " + t.getMessage());
|
289
|
+
}
|
258
290
|
}
|
259
291
|
|
260
|
-
|
261
292
|
private static void create_bundle_config(String app_root, String gem_path) throws IOException {
|
262
293
|
File bundle_config = new File(app_root + File.separator + ".bundle");
|
263
294
|
bundle_config.mkdir();
|
data/lib/jarbler/builder.rb
CHANGED
@@ -19,12 +19,8 @@ module Jarbler
|
|
19
19
|
app_root = Dir.pwd
|
20
20
|
debug "Project dir: #{app_root}"
|
21
21
|
|
22
|
-
source_and_target = if config.compile_java_version
|
23
|
-
"-source #{config.compile_java_version} -target #{config.compile_java_version}"
|
24
|
-
end
|
25
|
-
|
26
22
|
ruby_minor_version = copy_jruby_jars_to_staging(staging_dir) # Copy the jruby jars to the staging directory
|
27
|
-
exec_command "javac -nowarn -Xlint:deprecation #{
|
23
|
+
exec_command "javac -nowarn -Xlint:deprecation #{config.java_opts} -d #{staging_dir} #{__dir__}/JarMain.java" # Compile the Java files
|
28
24
|
|
29
25
|
# Copy the application project to the staging directory
|
30
26
|
FileUtils.mkdir_p("#{staging_dir}/app_root")
|
@@ -328,7 +324,10 @@ module Jarbler
|
|
328
324
|
debug "Compile Ruby file #{ruby_file}"
|
329
325
|
full_file_name = File.join(Dir.pwd, ruby_file) # full name including path is required by the JRuby compiler
|
330
326
|
begin
|
331
|
-
|
327
|
+
args = [full_file_name]
|
328
|
+
args.concat(config.jrubyc_opts)
|
329
|
+
args << "-t#{File.dirname(full_file_name)}" # target directory for the compiled class file
|
330
|
+
status = JRuby::Compiler::compile_argv(args) # compile the Ruby file
|
332
331
|
if status == 0
|
333
332
|
File.delete(full_file_name) # remove the original Ruby file to ensure that the compiled class file is used
|
334
333
|
else
|
@@ -337,6 +336,7 @@ module Jarbler
|
|
337
336
|
rescue Exception => e
|
338
337
|
puts "Error compiling Ruby file '#{ruby_file}': #{e.class}:#{e.message}"
|
339
338
|
puts "'#{ruby_file}' is not compiled and will be included in the jar file as original Ruby file"
|
339
|
+
puts e.backtrace.join("\n")
|
340
340
|
end
|
341
341
|
end
|
342
342
|
rescue Exception => e
|
data/lib/jarbler/config.rb
CHANGED
@@ -3,7 +3,17 @@ require 'json'
|
|
3
3
|
|
4
4
|
module Jarbler
|
5
5
|
class Config
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :compile_java_version, # compile_java_version only for backward compatibility, use java_opts instead
|
7
|
+
:compile_ruby_files,
|
8
|
+
:excludes,
|
9
|
+
:excludes_from_compile,
|
10
|
+
:executable,
|
11
|
+
:executable_params,
|
12
|
+
:includes,
|
13
|
+
:java_opts,
|
14
|
+
:jar_name,
|
15
|
+
:jrubyc_opts,
|
16
|
+
:jruby_version
|
7
17
|
|
8
18
|
CONFIG_FILE = 'config/jarble.rb'
|
9
19
|
# create instance of Config class with defaults or from config file
|
@@ -19,8 +29,9 @@ module Jarbler
|
|
19
29
|
config = Config.new
|
20
30
|
end
|
21
31
|
config.define_jruby_version
|
32
|
+
|
22
33
|
# Replace .rb with .class if compile_ruby_files is true
|
23
|
-
config.executable = config.executable.sub(/\.rb$/, '.class') if config.compile_ruby_files
|
34
|
+
config.executable = config.executable.sub(/\.rb$/, '.class') if config.compile_ruby_files && !config.excludes_from_compile.include?("app_root#{File::SEPARATOR}#{config.executable}")
|
24
35
|
|
25
36
|
config.validate_values
|
26
37
|
|
@@ -31,7 +42,6 @@ module Jarbler
|
|
31
42
|
puts "No configuration file found at #{File.join(Dir.pwd, CONFIG_FILE)}. Using default values."
|
32
43
|
end
|
33
44
|
puts "Used configuration values are:"
|
34
|
-
puts " compile_java_version: #{config.compile_java_version}" if config.compile_ruby_files
|
35
45
|
puts " compile_ruby_files: #{config.compile_ruby_files}"
|
36
46
|
puts " excludes: #{config.excludes}"
|
37
47
|
puts " excludes_from_compile: #{config.excludes_from_compile}" if config.compile_ruby_files
|
@@ -39,6 +49,8 @@ module Jarbler
|
|
39
49
|
puts " executable_params: #{config.executable_params}"
|
40
50
|
puts " includes: #{config.includes}"
|
41
51
|
puts " jar_name: #{config.jar_name}"
|
52
|
+
puts " java_opts: #{config.java_opts}"
|
53
|
+
puts " jrubyc_opts: #{config.jruby_version}" if config.compile_ruby_files
|
42
54
|
puts " jruby_version: #{config.jruby_version}"
|
43
55
|
puts ""
|
44
56
|
config
|
@@ -46,13 +58,15 @@ module Jarbler
|
|
46
58
|
|
47
59
|
def initialize
|
48
60
|
@compile_ruby_files = false
|
49
|
-
@compile_java_version = nil
|
61
|
+
@compile_java_version = nil # deprecated, use java_opts instead
|
50
62
|
@excludes = %w(tmp/cache tmp/pids tmp/sockets vendor/bundle vendor/cache vendor/ruby)
|
51
63
|
@excludes_from_compile = []
|
52
64
|
@executable = 'bin/rails'
|
53
65
|
@executable_params = %w(server -e production -p 8080)
|
54
66
|
@includes = %w(app bin config config.ru db Gemfile Gemfile.lock lib log public Rakefile script vendor tmp)
|
67
|
+
@java_opts = nil
|
55
68
|
@jar_name = File.basename(Dir.pwd) + '.jar'
|
69
|
+
@jrubyc_opts = []
|
56
70
|
@jruby_version = nil # determined automatically at runtime
|
57
71
|
# execute additional block if given
|
58
72
|
yield self if block_given?
|
@@ -61,22 +75,17 @@ module Jarbler
|
|
61
75
|
# Generate the template config file based on default values
|
62
76
|
def create_config_file
|
63
77
|
write_config_file("\
|
64
|
-
#
|
65
|
-
#
|
66
|
-
|
67
|
-
# Application directories or files to include in the jar file
|
68
|
-
# config.includes = #{includes}
|
69
|
-
# config.includes << 'additional'
|
78
|
+
# Compile the ruby files of the project to Java .class files with JRuby's ahead-of-time compiler?
|
79
|
+
# the original ruby files are not included in the jar file, so source code is not visible
|
80
|
+
# config.compile_ruby_files = #{compile_ruby_files}
|
70
81
|
|
71
82
|
# Application directories or files to exclude from the jar file
|
72
83
|
# config.excludes = #{excludes}
|
73
84
|
# config.excludes << 'additional'
|
74
85
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
# config.jruby_version = '9.2.3.0'
|
79
|
-
# config.jruby_version = nil
|
86
|
+
# Directories or files to exclude from the compilation if compile_ruby_files = true
|
87
|
+
# The paths map to the final location of files or dirs in the jar file, e.g. config.excludes_from_compile = ['gems', 'app_root/app/models']
|
88
|
+
# config.excludes_from_compile = #{excludes_from_compile}
|
80
89
|
|
81
90
|
# The Ruby executable file to run, e.g. 'bin/rails' or 'bin/rake'
|
82
91
|
# config.executable = '#{executable}'
|
@@ -84,18 +93,28 @@ module Jarbler
|
|
84
93
|
# Additional command line parameters for the Ruby executable
|
85
94
|
# config.executable_params = #{executable_params}
|
86
95
|
|
87
|
-
#
|
88
|
-
#
|
89
|
-
# config.
|
96
|
+
# Application directories or files to include in the jar file
|
97
|
+
# config.includes = #{includes}
|
98
|
+
# config.includes << 'additional'
|
90
99
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# if not set then it generates the class file version according to your current Java version
|
94
|
-
# config.compile_java_version = '1.8'
|
100
|
+
# Name of the generated jar file
|
101
|
+
# config.jar_name = '#{jar_name}'
|
95
102
|
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
103
|
+
# Additional options for the Java compiler (javac).
|
104
|
+
# Used for the compilation of the jar file bootstrap class JarMain.java.
|
105
|
+
# Also used for the optional compilation of the ruby files if compile_ruby_files = true.
|
106
|
+
# E.g. control the class file version of the generated jar file with -source and -target
|
107
|
+
# config.java_opts = '-target 1.8 -source 1.8'
|
108
|
+
|
109
|
+
# Additional options for the JRuby compiler (jrubyc).
|
110
|
+
# Used for the optional compilation of the ruby files if compile_ruby_files = true.
|
111
|
+
# config.jrubyc_opts = ['--javac'. '--verbose']
|
112
|
+
|
113
|
+
# Use certain JRuby version
|
114
|
+
# if not set (nil) then the version defined in .ruby-version
|
115
|
+
# if not JRuby version defined here or in .ruby-version then the latest available JRuby version is used
|
116
|
+
# config.jruby_version = '9.2.3.0'
|
117
|
+
# config.jruby_version = nil
|
99
118
|
|
100
119
|
".split("\n"))
|
101
120
|
end
|
@@ -164,6 +183,7 @@ module Jarbler
|
|
164
183
|
end
|
165
184
|
|
166
185
|
def validate_values
|
186
|
+
raise "compile_java_version is not valid any more! Use config.java_opts instead with -source and -target." if compile_java_version
|
167
187
|
raise "Invalid config value for jar name: #{jar_name}" unless jar_name =~ /\w+/
|
168
188
|
raise "Invalid config value for executable: #{executable}" unless executable =~ /\w+/
|
169
189
|
raise "Invalid config value for executable params: #{executable_params}" unless executable_params.is_a?(Array)
|
@@ -172,6 +192,7 @@ module Jarbler
|
|
172
192
|
raise "Invalid config value for compile_ruby_files: #{compile_ruby_files}" unless [true, false].include?(compile_ruby_files)
|
173
193
|
raise "compile_ruby_files = true is supported only with JRuby! Current runtime is '#{RUBY_ENGINE}'" if compile_ruby_files && (defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby')
|
174
194
|
raise "Invalid config value for excludes_from_compile: #{excludes_from_compile}" unless excludes_from_compile.is_a?(Array)
|
195
|
+
raise "Invalid config value for jrubyc_opts, should by Array: #{jrubyc_opts}" unless jrubyc_opts.is_a?(Array)
|
175
196
|
raise "Invalid config value for jruby_version: #{jruby_version}" unless jruby_version.nil? || jruby_version =~ /\d+\.\d+\.\d+\.\d+/
|
176
197
|
end
|
177
198
|
end
|
data/lib/jarbler/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jarbler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Ramm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Pack a Ruby app combined with JRuby runtime and all its Gem dependencies
|
14
14
|
into a jar file to simply run the app on any Java platform by '> java -jar file.jar'
|