jarbler 0.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/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/Rakefile +16 -0
- data/bin/jarble +18 -0
- data/build_gem.sh +18 -0
- data/jarbler.gemspec +37 -0
- data/lib/jarbler/JarMain.java +209 -0
- data/lib/jarbler/builder.rb +256 -0
- data/lib/jarbler/config.rb +108 -0
- data/lib/jarbler/version.rb +6 -0
- data/lib/jarbler.rb +23 -0
- data/sig/jarbler.rbs +4 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bc9683c3fba322e4476646046c54b7c61ae7b6fa3d4c9865d1223fad0db590ee
|
4
|
+
data.tar.gz: 5e64e298302111c5fbcc606c8aad86ba93b454daece192318c33b3eca6696ccb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0edc420a59cb4e48364c0155c0cee0f2449b32264de8f3967cf790b82df541a367d7206b4a5dbf0461561be9e88fee79d8ba7bdc737d6ca2eda951a97cc5e0d9
|
7
|
+
data.tar.gz: 4e7d227903baacaaf8960f333c9f3beaee96dd5c399a45ab724620af04fbdfcbb5ed1ccce9a1955907d563ce2888b1d9a0b174dc2dadb85d557960e30ca7608c
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-3.2.2
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in jarbler.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
group(:test) do
|
11
|
+
gem 'minitest'
|
12
|
+
gem 'minitest-reporters'
|
13
|
+
# needed for minitests
|
14
|
+
gem 'jruby-jars'
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Peter Ramm
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Jarbler
|
2
|
+
Pack a Ruby application into an executable jar file.
|
3
|
+
|
4
|
+
Jarbler allows you to create an self executing Java jar file containing your Ruby application.
|
5
|
+
|
6
|
+
This tool is inspired by the widely used jRuby runner Warbler.
|
7
|
+
In contrast to Warbler no Java servlet container is needed for execution.
|
8
|
+
Instead the configured executable is executed using the jRuby runtime jars.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Install the gem and add to the application's Gemfile by executing:
|
13
|
+
|
14
|
+
$ bundle add jarbler --group "development"
|
15
|
+
|
16
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
17
|
+
|
18
|
+
$ gem install jarbler
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
To create a jar file simply run "jarble" in your application's root directory.
|
23
|
+
|
24
|
+
$ jarble
|
25
|
+
|
26
|
+
To adjust Jarbler's configuration, modify the settings in config file ´config/jarble.rb´. The template for this config file you create by executing
|
27
|
+
|
28
|
+
$ jarble config
|
29
|
+
|
30
|
+
### Preconditions
|
31
|
+
* The Ruby app should be capable of running with jRuby
|
32
|
+
* Gems with native extensions should not be used (e.g. sassc)
|
33
|
+
* if needed for development or test such Gems with native extensions should be moved to the development and test group in the Gemfile.
|
34
|
+
* Otherwise the created jar file may not be executable on all platforms and Java versions.
|
35
|
+
|
36
|
+
## Run the created jar file
|
37
|
+
The jar file created by Jarbler can be executed by
|
38
|
+
|
39
|
+
$ java -jar <jar filename>
|
40
|
+
|
41
|
+
Additional command line parameters are passed through to the executed Ruby app (like "-p 8900" for different network port number with bin/rails)
|
42
|
+
|
43
|
+
## Configuration
|
44
|
+
|
45
|
+
The file config/jarble.rb contains the configuration for Jarbler.
|
46
|
+
To create a template config file with information about the supported configuration options, execute:
|
47
|
+
|
48
|
+
$ jarble config
|
49
|
+
|
50
|
+
The default configuration supports Ruby on Rails applications.<br>
|
51
|
+
The executable is set to "bin/rails" by default.<br>
|
52
|
+
The default executable parameters are "server -p 8080 -e production".
|
53
|
+
|
54
|
+
## Troubleshooting
|
55
|
+
* Set DEBUG=true in environment to get additional runtime information
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rammpeter/jarbler. <br>
|
60
|
+
Any feedback about usage experience or missing features is also appreciated.
|
61
|
+
|
62
|
+
## License
|
63
|
+
|
64
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
task default: %i[]
|
5
|
+
|
6
|
+
|
7
|
+
require 'bundler/gem_tasks'
|
8
|
+
|
9
|
+
require 'rake/testtask'
|
10
|
+
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = false
|
16
|
+
end
|
data/bin/jarble
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'jarbler'
|
4
|
+
|
5
|
+
# call config if arguments are passed and argument = 'config'
|
6
|
+
# otherwise call Jarbler.run
|
7
|
+
if ARGV.empty?
|
8
|
+
Jarbler.run
|
9
|
+
else
|
10
|
+
if ARGV[0] == 'config' && ARGV.length == 1
|
11
|
+
Jarbler.config
|
12
|
+
else
|
13
|
+
puts "Invalid argument! Valid arguments are:"
|
14
|
+
puts "- no argument: build the jar"
|
15
|
+
puts "- config: create a template config file at #{Jarbler::Config::CONFIG_FILE}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
data/build_gem.sh
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Steps for creating gem
|
2
|
+
rake test
|
3
|
+
if [ $? -ne 0 ]; then
|
4
|
+
echo "Tests failed."
|
5
|
+
exit 1
|
6
|
+
fi
|
7
|
+
|
8
|
+
gem build
|
9
|
+
if [ $? -ne 0 ]; then
|
10
|
+
echo "Gem build failed."
|
11
|
+
exit 1
|
12
|
+
fi
|
13
|
+
|
14
|
+
gem install jarbler-0.1.0.gem
|
15
|
+
if [ $? -ne 0 ]; then
|
16
|
+
echo "Gem install failed."
|
17
|
+
exit 1
|
18
|
+
fi
|
data/jarbler.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/jarbler/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "jarbler"
|
7
|
+
spec.version = Jarbler::VERSION
|
8
|
+
spec.authors = ["Peter Ramm"]
|
9
|
+
spec.email = ["Peter@ramm-oberhermsdorf.de"]
|
10
|
+
|
11
|
+
spec.summary = "Create jar file from Rails app"
|
12
|
+
spec.description = "Pack a Rails application into an executable jar file"
|
13
|
+
spec.homepage = "https://github.com/rammpeter/jarbler"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/rammpeter/jarbler"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/rammpeter/jarbler/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
spec.bindir = "bin"
|
29
|
+
spec.executables << 'jarble'
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
34
|
+
|
35
|
+
# For more information and examples about making a new gem, check out our
|
36
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import java.io.File;
|
2
|
+
import java.io.FileInputStream;
|
3
|
+
import java.io.FileOutputStream;
|
4
|
+
import java.io.IOException;
|
5
|
+
import java.io.InputStream;
|
6
|
+
import java.util.Enumeration;
|
7
|
+
import java.util.UUID;
|
8
|
+
import java.util.jar.JarEntry;
|
9
|
+
import java.util.jar.JarFile;
|
10
|
+
import java.util.jar.JarInputStream;
|
11
|
+
import java.util.jar.JarOutputStream;
|
12
|
+
import java.util.ArrayList;
|
13
|
+
import java.util.Properties;
|
14
|
+
import java.util.zip.ZipEntry;
|
15
|
+
import java.util.zip.ZipInputStream;
|
16
|
+
import java.lang.reflect.Method;
|
17
|
+
import java.net.URL;
|
18
|
+
import java.net.URLClassLoader;
|
19
|
+
import java.lang.reflect.InvocationTargetException;
|
20
|
+
import java.lang.reflect.Constructor;
|
21
|
+
import java.net.MalformedURLException;
|
22
|
+
import java.lang.ClassNotFoundException;
|
23
|
+
import java.lang.NoSuchMethodException;
|
24
|
+
import java.lang.IllegalAccessException;
|
25
|
+
import java.lang.InstantiationException;
|
26
|
+
|
27
|
+
|
28
|
+
class JarMain {
|
29
|
+
|
30
|
+
// executed by java -jar <jar file name>
|
31
|
+
// No arguments are passed
|
32
|
+
public static void main(String[] args) {
|
33
|
+
debug("Start java process in jar file");
|
34
|
+
if (args.length > 0) {
|
35
|
+
debug("Java command line arguments are: ");
|
36
|
+
for (String arg : args) {
|
37
|
+
debug(" - " + arg);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
// create a new folder in temp directory
|
41
|
+
File newFolder = new File(System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID().toString());
|
42
|
+
newFolder.mkdir();
|
43
|
+
try {
|
44
|
+
// Get the path of the jar file
|
45
|
+
String jarPath = JarMain.class.getProtectionDomain().getCodeSource().getLocation().getPath();
|
46
|
+
|
47
|
+
// remove the leading slash if path is a windows path
|
48
|
+
String os = System.getProperty("os.name").toLowerCase();
|
49
|
+
boolean isWindows = os.contains("windows");
|
50
|
+
if (os.contains("windows") && jarPath.startsWith("/") && jarPath.indexOf(':') != -1) {
|
51
|
+
jarPath = jarPath.substring(1); // remove the leading slash
|
52
|
+
}
|
53
|
+
|
54
|
+
// extract the jarFile by executing jar -xf jarFileName
|
55
|
+
System.out.println("Extracting files from "+jarPath+" to "+ newFolder.getAbsolutePath());
|
56
|
+
unzip(jarPath, newFolder.getAbsolutePath());
|
57
|
+
|
58
|
+
|
59
|
+
// get the file name of the jruby jar file in newFolder
|
60
|
+
File[] files = newFolder.listFiles();
|
61
|
+
// get the existing file from files where prefix is jruby-core* and suffix is .jar
|
62
|
+
File jrubyCoreFile = null;
|
63
|
+
File jrubyStdlibFile = null;
|
64
|
+
for (File file : files) {
|
65
|
+
if (file.getName().startsWith("jruby-core") && file.getName().endsWith(".jar")) {
|
66
|
+
jrubyCoreFile = file;
|
67
|
+
}
|
68
|
+
if (file.getName().startsWith("jruby-stdlib") && file.getName().endsWith(".jar")) {
|
69
|
+
jrubyStdlibFile = file;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
debug("jruby core jar file is : "+ jrubyCoreFile.getAbsolutePath());
|
73
|
+
debug("jruby stdlib jar file is: "+ jrubyStdlibFile.getAbsolutePath());
|
74
|
+
|
75
|
+
// read the property file and get the port number
|
76
|
+
String executable = "Executable definition missing"; // Default if nothing else is specified
|
77
|
+
String executable_params = "";
|
78
|
+
|
79
|
+
Properties prop = new Properties();
|
80
|
+
prop.load(new FileInputStream(newFolder.getAbsolutePath()+File.separator+"jarbler.properties"));
|
81
|
+
executable = prop.getProperty("jarbler.executable");
|
82
|
+
executable_params = prop.getProperty("jarbler.executable_params");
|
83
|
+
|
84
|
+
// throw exception if executable is null
|
85
|
+
if (executable == null) {
|
86
|
+
throw new RuntimeException("Property 'executable' definition missing in jarbler.properties");
|
87
|
+
}
|
88
|
+
|
89
|
+
System.setProperty("GEM_PATH", newFolder.getAbsolutePath()+File.separator+"gems"); // not really necessray for Rails
|
90
|
+
System.setProperty("GEM_HOME", newFolder.getAbsolutePath()+File.separator+"gems"); // not really necessray for Rails
|
91
|
+
System.setProperty("BUNDLE_PATH", newFolder.getAbsolutePath()+File.separator+"gems"); // this drives bundler for rails app
|
92
|
+
System.setProperty("BUNDLE_WITHOUT", "test:development"); // exclude test and development dependencies from Gemfile check
|
93
|
+
|
94
|
+
// Load the Jar file
|
95
|
+
URLClassLoader classLoader = new URLClassLoader(new URL[]{
|
96
|
+
new URL("file://" + jrubyCoreFile.getAbsolutePath()),
|
97
|
+
new URL("file://" + jrubyStdlibFile.getAbsolutePath())
|
98
|
+
});
|
99
|
+
|
100
|
+
// Load the class
|
101
|
+
Class<?> clazz = classLoader.loadClass("org.jruby.Main");
|
102
|
+
|
103
|
+
// Get the method
|
104
|
+
Method mainMethod = clazz.getMethod("main", String[].class);
|
105
|
+
|
106
|
+
// Create an instance of the class
|
107
|
+
Constructor<?> constructor = clazz.getConstructor();
|
108
|
+
Object instance = (Object) constructor.newInstance();
|
109
|
+
//Object instance = clazz.newInstance();
|
110
|
+
|
111
|
+
// Prepare the argument list
|
112
|
+
ArrayList<String> mainArgs = new ArrayList<String>();
|
113
|
+
mainArgs.add(executable);
|
114
|
+
if (executable_params != null) {
|
115
|
+
for (String param : executable_params.split(" ")) {
|
116
|
+
mainArgs.add(param);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
// add possible command line arguments
|
120
|
+
if (args.length > 0) {
|
121
|
+
for (String arg : args) {
|
122
|
+
mainArgs.add(arg);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
debug("jRuby program starts with the following arguments: ");
|
127
|
+
for (String arg : mainArgs) {
|
128
|
+
debug(" - " + arg);
|
129
|
+
}
|
130
|
+
|
131
|
+
System.setProperty("user.dir", newFolder.getAbsolutePath()+File.separator+"app_root");
|
132
|
+
// call the method org.jruby.Main.main
|
133
|
+
mainMethod.invoke(null, (Object)mainArgs.toArray(new String[mainArgs.size()]));
|
134
|
+
} catch (Exception e) {
|
135
|
+
e.getCause().printStackTrace();
|
136
|
+
} finally {
|
137
|
+
// remove the temp directory newFolder
|
138
|
+
debug("jRuby program terminated, removing temporary folder "+ newFolder.getAbsolutePath());
|
139
|
+
deleteFolder(newFolder);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
private static void unzip(String fileZip, String destination) throws IOException {
|
144
|
+
File destDir = new File(destination);
|
145
|
+
|
146
|
+
byte[] buffer = new byte[1024];
|
147
|
+
ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip));
|
148
|
+
ZipEntry zipEntry = zis.getNextEntry();
|
149
|
+
while (zipEntry != null) {
|
150
|
+
while (zipEntry != null) {
|
151
|
+
File newFile = newFile(destDir, zipEntry);
|
152
|
+
if (zipEntry.isDirectory()) {
|
153
|
+
if (!newFile.isDirectory() && !newFile.mkdirs()) {
|
154
|
+
throw new IOException("Failed to create directory " + newFile);
|
155
|
+
}
|
156
|
+
} else {
|
157
|
+
// fix for Windows-created archives
|
158
|
+
File parent = newFile.getParentFile();
|
159
|
+
if (!parent.isDirectory() && !parent.mkdirs()) {
|
160
|
+
throw new IOException("Failed to create directory " + parent);
|
161
|
+
}
|
162
|
+
|
163
|
+
// write file content
|
164
|
+
FileOutputStream fos = new FileOutputStream(newFile);
|
165
|
+
int len;
|
166
|
+
while ((len = zis.read(buffer)) > 0) {
|
167
|
+
fos.write(buffer, 0, len);
|
168
|
+
}
|
169
|
+
fos.close();
|
170
|
+
}
|
171
|
+
zipEntry = zis.getNextEntry();
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
zis.closeEntry();
|
176
|
+
zis.close();
|
177
|
+
}
|
178
|
+
|
179
|
+
private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
|
180
|
+
File destFile = new File(destinationDir, zipEntry.getName());
|
181
|
+
|
182
|
+
String destDirPath = destinationDir.getCanonicalPath();
|
183
|
+
String destFilePath = destFile.getCanonicalPath();
|
184
|
+
|
185
|
+
if (!destFilePath.startsWith(destDirPath + File.separator)) {
|
186
|
+
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
|
187
|
+
}
|
188
|
+
|
189
|
+
return destFile;
|
190
|
+
}
|
191
|
+
|
192
|
+
private static void debug(String msg) {
|
193
|
+
if (System.getenv("DEBUG") != null) {
|
194
|
+
System.out.println(msg);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
private static void deleteFolder(File file){
|
199
|
+
for (File subFile : file.listFiles()) {
|
200
|
+
if(subFile.isDirectory()) {
|
201
|
+
deleteFolder(subFile);
|
202
|
+
} else {
|
203
|
+
subFile.delete();
|
204
|
+
}
|
205
|
+
}
|
206
|
+
file.delete();
|
207
|
+
}
|
208
|
+
|
209
|
+
}
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'find'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Jarbler
|
6
|
+
class Builder
|
7
|
+
# Execute all functions needed to build the jar file
|
8
|
+
# Should be executed in application directory of Rails/Ruby application
|
9
|
+
# @return [void]
|
10
|
+
def build_jar
|
11
|
+
# create a temporary directory for staging
|
12
|
+
staging_dir = Dir.mktmpdir
|
13
|
+
|
14
|
+
jarbler_lib_dir = __dir__
|
15
|
+
app_root = Dir.pwd
|
16
|
+
debug "Project dir: #{app_root}"
|
17
|
+
|
18
|
+
# TODO: transform to internal bundler API call (check if jruby is installed + install if not)
|
19
|
+
exec_command "gem install --no-doc jruby-jars -v #{config.jruby_version}" # Ensure that jruby-jars are installed in the requested version
|
20
|
+
gem_search_locations = collect_gem_search_locations(app_root)
|
21
|
+
ruby_version = copy_jruby_jars_to_staging(staging_dir, gem_search_locations) # Copy the jruby jars to the staging directory
|
22
|
+
exec_command "javac -nowarn -Xlint:deprecation -source 8 -target 8 -d #{staging_dir} #{jarbler_lib_dir}/JarMain.java" # Compile the Java files
|
23
|
+
|
24
|
+
# Copy the application project to the staging directory
|
25
|
+
FileUtils.mkdir_p("#{staging_dir}/app_root")
|
26
|
+
config.includes.each do |dir|
|
27
|
+
file_utils_copy("#{app_root}/#{dir}", "#{staging_dir}/app_root") if File.exist?("#{app_root}/#{dir}")
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get the needed Gems
|
31
|
+
raise "Gemfile.lock not found in #{app_root}" unless File.exist?("#{app_root}/Gemfile.lock")
|
32
|
+
|
33
|
+
gem_target_location = "#{staging_dir}/gems/jruby/#{ruby_version}"
|
34
|
+
FileUtils.mkdir_p("#{gem_target_location}/gems")
|
35
|
+
FileUtils.mkdir_p("#{gem_target_location}/specifications")
|
36
|
+
|
37
|
+
needed_gems = gem_dependencies # get the full names of the dependencies
|
38
|
+
needed_gems.each do |gem_full_name|
|
39
|
+
copy_gem_to_staging(gem_full_name, gem_target_location, gem_search_locations)
|
40
|
+
end
|
41
|
+
|
42
|
+
Dir.chdir(staging_dir) do
|
43
|
+
# create the manifest file
|
44
|
+
File.open('Manifest.txt', 'w') do |file|
|
45
|
+
file.write("Comment: created by Jarbler (https://github.com/rammpeter/jarbler)\n")
|
46
|
+
file.write("Main-Class: JarMain\n")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Write java properties file for use in JarMain.java
|
50
|
+
File.open('jarbler.properties', 'w') do |file|
|
51
|
+
file.write("jarbler.executable=#{config.executable}\n")
|
52
|
+
# write a list of strings into property file delimited by space
|
53
|
+
java_executable_params = ''
|
54
|
+
config.executable_params.each do |param|
|
55
|
+
java_executable_params += "#{param} "
|
56
|
+
end
|
57
|
+
file.write("jarbler.executable_params=#{java_executable_params.strip}\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
# remove files and directories from excludes, if they exist (after copying the rails project and the gems)
|
61
|
+
config.excludes.each do |exclude|
|
62
|
+
to_remove = "app_root/#{exclude}"
|
63
|
+
if File.exist?(to_remove)
|
64
|
+
debug "Removing #{to_remove} from staging directory"
|
65
|
+
FileUtils.rm_rf(to_remove)
|
66
|
+
else
|
67
|
+
debug "Not removing #{to_remove} from staging directory, because it does not exist"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
exec_command "jar cfm #{config.jar_name} Manifest.txt *" # create the jar file
|
72
|
+
|
73
|
+
# place the jar in project directory
|
74
|
+
file_utils_copy(config.jar_name, app_root)
|
75
|
+
puts "Created jar file #{app_root}/#{config.jar_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# remove temporary directory staging_dir
|
79
|
+
FileUtils.remove_entry staging_dir
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Find the locations where Gems are installed
|
86
|
+
# @param [String] app_root Application root directory
|
87
|
+
# @return [Array] Array of Gem locations
|
88
|
+
def collect_gem_search_locations(app_root)
|
89
|
+
# Search locations of gems in Gemfile.lock
|
90
|
+
possible_gem_search_locations = []
|
91
|
+
# Add possible local config first in search list
|
92
|
+
possible_gem_search_locations << bundle_config_bundle_path(app_root) if bundle_config_bundle_path(app_root)
|
93
|
+
ENV['GEM_PATH'].split(':').each do |gem_path|
|
94
|
+
possible_gem_search_locations << gem_path
|
95
|
+
end
|
96
|
+
debug "Possible Gem locations: #{possible_gem_search_locations}"
|
97
|
+
gem_search_locations = []
|
98
|
+
# Check where inside this location the gems may be installed
|
99
|
+
possible_gem_search_locations.each do |gem_search_location|
|
100
|
+
valid_gem_search_location = nil # No valid path found yet
|
101
|
+
Find.find(gem_search_location) do |path|
|
102
|
+
if File.directory?(path) && File.exist?("#{path}/specifications") && File.exist?("#{path}/gems")
|
103
|
+
valid_gem_search_location = path # Found a valid path
|
104
|
+
Find.prune # Do not search deeper
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if valid_gem_search_location
|
108
|
+
gem_search_locations << valid_gem_search_location
|
109
|
+
else
|
110
|
+
debug "No valid gem location found in #{gem_search_location}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
debug "Valid Gem locations: #{gem_search_locations}"
|
114
|
+
gem_search_locations
|
115
|
+
end
|
116
|
+
|
117
|
+
# Copy the Gem elements to the staging directory
|
118
|
+
# @param [String] gem_full_name Full name of the Gem including version and platform
|
119
|
+
# @param [String] staging_dir Path to the staging directory
|
120
|
+
# @param [Array] gem_search_locations Array of Gem locations
|
121
|
+
# @return [void]
|
122
|
+
def copy_gem_to_staging(gem_full_name, gem_target_location, gem_search_locations)
|
123
|
+
gem_search_locations.each do |gem_search_location|
|
124
|
+
gem_dir = "#{gem_search_location}/gems/#{gem_full_name}"
|
125
|
+
if File.exist?(gem_dir)
|
126
|
+
file_utils_copy(gem_dir, "#{gem_target_location}/gems")
|
127
|
+
file_utils_copy("#{gem_search_location}/specifications/#{gem_full_name}.gemspec", "#{gem_target_location}/specifications")
|
128
|
+
return
|
129
|
+
end
|
130
|
+
end
|
131
|
+
raise "Gem #{gem_name} (#{gem_version}) not found in any of the following locations:\n#{gem_search_locations.join("\n")}"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Read the default/production dependencies from Gemfile.lock and Gemfile
|
135
|
+
# @return [Array] Array with full names of dependencies
|
136
|
+
def gem_dependencies
|
137
|
+
needed_gems = []
|
138
|
+
lockfile_specs = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile)).specs
|
139
|
+
|
140
|
+
Bundler.setup # Load Gems specified in Gemfile
|
141
|
+
# filter Gems needed for production
|
142
|
+
gemfile_specs = Bundler.definition.dependencies.select do |d|
|
143
|
+
d.groups.include?(:default) || d.groups.include?(:production)
|
144
|
+
end
|
145
|
+
|
146
|
+
debug "Gems from Gemfile needed for production:"
|
147
|
+
gemfile_specs.each do |gemfile_spec|
|
148
|
+
# find lockfile record for Gemfile spec
|
149
|
+
lockfile_spec = lockfile_specs.find { |lockfile_spec| lockfile_spec.name == gemfile_spec.name }
|
150
|
+
if lockfile_spec
|
151
|
+
needed_gems << lockfile_spec.full_name unless needed_gems.include?(lockfile_spec.full_name)
|
152
|
+
debug "Direct Gem dependency: #{lockfile_spec.full_name}"
|
153
|
+
add_indirect_dependencies(lockfile_specs, lockfile_spec, needed_gems)
|
154
|
+
else
|
155
|
+
debug "Gem #{gemfile_spec.name} not found in Gemfile.lock"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
needed_gems.uniq.sort
|
159
|
+
end
|
160
|
+
|
161
|
+
# recurively find all indirect dependencies
|
162
|
+
# @param [Array] lockfile_specs Array of Bundler::LockfileParser::Spec objects
|
163
|
+
# @param [Bundler::LockfileParser::Spec] lockfile_spec current lockfile spec to check for their dependencies
|
164
|
+
# @param [Array] needed_gems Array with full names of already found dependencies, add findings here
|
165
|
+
# @return [void]
|
166
|
+
def add_indirect_dependencies(lockfile_specs, lockfile_spec, needed_gems)
|
167
|
+
lockfile_spec.dependencies.each do |lockfile_spec_dep|
|
168
|
+
lockfile_spec_found = lockfile_specs.find { |lockfile_spec| lockfile_spec.name == lockfile_spec_dep.name }
|
169
|
+
if lockfile_spec_found
|
170
|
+
debug "Indirect Gem dependency from #{lockfile_spec.full_name}: #{lockfile_spec_found.full_name}"
|
171
|
+
unless needed_gems.include?(lockfile_spec_found.full_name)
|
172
|
+
needed_gems << lockfile_spec_found.full_name
|
173
|
+
add_indirect_dependencies(lockfile_specs, lockfile_spec_found, needed_gems)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
debug "Gem #{lockfile_spec_dep.name} not found in Gemfile.lock"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
def debug(msg)
|
181
|
+
puts msg if ENV['DEBUG']
|
182
|
+
end
|
183
|
+
|
184
|
+
def config
|
185
|
+
unless defined? @config
|
186
|
+
@config = Config.create
|
187
|
+
debug("Config attributes:")
|
188
|
+
@config.instance_variables.each do |var|
|
189
|
+
debug("#{var}: #{@config.instance_variable_get(var)}")
|
190
|
+
end
|
191
|
+
debug ""
|
192
|
+
end
|
193
|
+
@config
|
194
|
+
end
|
195
|
+
|
196
|
+
# Check if there is an additional local bundle path in .bundle/config
|
197
|
+
def bundle_config_bundle_path(rails_root)
|
198
|
+
bundle_path = nil # default
|
199
|
+
if File.exist?("#{rails_root}/.bundle/config")
|
200
|
+
bundle_config = YAML.load_file("#{rails_root}/.bundle/config")
|
201
|
+
if bundle_config && bundle_config['BUNDLE_PATH']
|
202
|
+
bundle_path = "#{rails_root}/#{bundle_config['BUNDLE_PATH']}"
|
203
|
+
debug "Local Gem path configured in #{rails_root}/.bundle/config: #{bundle_path}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
bundle_path
|
207
|
+
end
|
208
|
+
|
209
|
+
# Copy the jruby-jars to the staging directory
|
210
|
+
# @param [String] staging_dir Path to the staging directory
|
211
|
+
# @param [Array] gem_search_locations Array of Gem locations to look for jRuby jars
|
212
|
+
# @return [String] the ruby version of the jRuby jars
|
213
|
+
def copy_jruby_jars_to_staging(staging_dir, gem_search_locations)
|
214
|
+
jruby_jars_location = nil
|
215
|
+
gem_search_locations.each do |gem_search_location|
|
216
|
+
gem_dir = "#{gem_search_location}/gems/jruby-jars-#{config.jruby_version}"
|
217
|
+
if File.exist?(gem_dir)
|
218
|
+
jruby_jars_location = gem_dir
|
219
|
+
break
|
220
|
+
end
|
221
|
+
end
|
222
|
+
raise "Could not determine location of jRuby jars for release '#{config.jruby_version}' in the following output:\n#{lines}" unless jruby_jars_location
|
223
|
+
file_utils_copy("#{jruby_jars_location}/lib/jruby-core-#{config.jruby_version}-complete.jar", staging_dir)
|
224
|
+
file_utils_copy("#{jruby_jars_location}/lib/jruby-stdlib-#{config.jruby_version}.jar", staging_dir)
|
225
|
+
|
226
|
+
# Get the according Ruby version for the current jRuby version
|
227
|
+
lines = exec_command "java -cp #{jruby_jars_location}/lib/jruby-core-#{config.jruby_version}-complete.jar org.jruby.Main --version"
|
228
|
+
match_result = lines.match(/\(.*\)/)
|
229
|
+
raise "Could not determine Ruby version for jRuby #{config.jruby_version} in following output:\n#{lines}" unless match_result
|
230
|
+
ruby_version = match_result[0].tr('()', '')
|
231
|
+
debug "Corresponding Ruby version for jRuby (#{config.jruby_version}): #{ruby_version}"
|
232
|
+
ruby_version
|
233
|
+
end
|
234
|
+
|
235
|
+
# Execute the command and return the output
|
236
|
+
def exec_command(command)
|
237
|
+
lines = `#{command}`
|
238
|
+
raise "Command \"#{command}\"failed with return code #{$?} and output:\n#{lines}" unless $?.success?
|
239
|
+
debug "Command \"#{command}\" executed successfully with following output:\n#{lines}"
|
240
|
+
lines
|
241
|
+
end
|
242
|
+
|
243
|
+
# Copy file or directory with error handling
|
244
|
+
def file_utils_copy(source, destination)
|
245
|
+
if File.exist?(source) && File.directory?(source)
|
246
|
+
FileUtils.cp_r(source, destination)
|
247
|
+
else
|
248
|
+
FileUtils.cp(source, destination)
|
249
|
+
end
|
250
|
+
rescue Exception
|
251
|
+
puts "Error copying #{source} to #{destination}"
|
252
|
+
raise
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Jarbler
|
2
|
+
class Config
|
3
|
+
attr_accessor :jar_name, :includes, :excludes, :jruby_version, :executable, :executable_params
|
4
|
+
|
5
|
+
CONFIG_FILE = 'config/jarble.rb'
|
6
|
+
# create instence of Config class with defaults or from config file
|
7
|
+
# Should be called from rails/ruby root directory
|
8
|
+
def self.create
|
9
|
+
if File.exist?(CONFIG_FILE)
|
10
|
+
config = eval(File.read(CONFIG_FILE), binding, CONFIG_FILE, 0)
|
11
|
+
else
|
12
|
+
config = Jarbler::Config.new
|
13
|
+
end
|
14
|
+
unless config.class == Jarbler::Config
|
15
|
+
Jarbler.debug "No valid config provided in #{CONFIG_FILE}! Using defaults."
|
16
|
+
config = Config.new
|
17
|
+
end
|
18
|
+
config.define_jruby_version
|
19
|
+
config
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@jar_name = File.basename(Dir.pwd) + '.jar'
|
24
|
+
@includes = %w(app bin config config.ru db Gemfile Gemfile.lock lib log public Rakefile script vendor tmp)
|
25
|
+
@excludes = %w(tmp/cache tmp/pids tmp/sockets vendor/bundle vendor/cache vendor/ruby)
|
26
|
+
@jruby_version = nil # determined automatically at runtime
|
27
|
+
@executable = 'bin/rails'
|
28
|
+
@executable_params = %w(server -e production -p 8080)
|
29
|
+
# execute additional block if given
|
30
|
+
yield self if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generate the template config file based on default values
|
34
|
+
def create_config_file
|
35
|
+
write_config_file("\
|
36
|
+
# Name of the generated jar file
|
37
|
+
# config.jar_name = '#{jar_name}'
|
38
|
+
|
39
|
+
# Application directories or files to include in the jar file
|
40
|
+
# config.includes = #{includes}
|
41
|
+
# config.includes << 'additional'
|
42
|
+
|
43
|
+
# Application directories or files to exclude from the jar file
|
44
|
+
# config.excludes = #{excludes}
|
45
|
+
# config.excludes << 'additional'
|
46
|
+
|
47
|
+
# Use certail jRuby version
|
48
|
+
# if not set (nil) then the version defined in .ruby-version
|
49
|
+
# if not jRuby version defined here or in .ruby-version then the latest available jRuby version is used
|
50
|
+
# config.jruby_version = '9.2.3.0'
|
51
|
+
# config.jruby_version = nil
|
52
|
+
|
53
|
+
# The Ruby executable file to run, e.g. 'bin/rails' or 'bin/rake'
|
54
|
+
# config.executable = '#{executable}'
|
55
|
+
|
56
|
+
# Additional command line parameters for the Ruby executable
|
57
|
+
# config.executable_params = #{executable_params}
|
58
|
+
".split("\n"))
|
59
|
+
end
|
60
|
+
|
61
|
+
# write a config file with the given lines
|
62
|
+
# if the file exists, it is overwritten
|
63
|
+
# if the file does not exist, it is created
|
64
|
+
# @param [Array] lines is an array of strings
|
65
|
+
def write_config_file(lines)
|
66
|
+
lines = [lines] unless lines.is_a?(Array)
|
67
|
+
FileUtils.mkdir_p('config')
|
68
|
+
raise "config file #{CONFIG_FILE} already exists in current directory! Please move file temporary and try again." if File.exist?(CONFIG_FILE)
|
69
|
+
File.open(CONFIG_FILE, 'w') do |file|
|
70
|
+
file.write("# Jarbler configuration, see https://github.com/rammpeter/jarbler\n")
|
71
|
+
file.write("# values in comments are the default values\n")
|
72
|
+
file.write("# uncomment and adjust if needed\n")
|
73
|
+
file.write(" \n")
|
74
|
+
file.write("Jarbler::Config.new do |config|\n")
|
75
|
+
lines.each do |line|
|
76
|
+
file.write(" #{line}\n")
|
77
|
+
end
|
78
|
+
file.write("end\n")
|
79
|
+
end
|
80
|
+
puts "Jarbler: Created config file #{Dir.pwd}/#{CONFIG_FILE}"
|
81
|
+
end
|
82
|
+
|
83
|
+
# define jRuby version if not set in config file
|
84
|
+
def define_jruby_version
|
85
|
+
unless @jruby_version # not defined in config file
|
86
|
+
if File.exist?('.ruby-version')
|
87
|
+
# read the file RAILS_ROOT/.ruby-version starting from char at position 6 to the end of the line
|
88
|
+
self.jruby_version = File.read('.ruby-version')[6..20].strip
|
89
|
+
debug "jRuby version from .ruby-version file: #{jruby_version}"
|
90
|
+
else
|
91
|
+
# no .ruby-version file, use jRuby version of the latest Gem
|
92
|
+
# Fetch the gem specification from Rubygems.org
|
93
|
+
command = "gem search --remote jruby-jars"
|
94
|
+
lines = `#{command}`
|
95
|
+
raise "Command \"#{command}\" failed with return code #{$?} and output:\n#{lines}" unless $?.success?
|
96
|
+
jruby_jars_line = lines.match(/^jruby-jars \((.*)\)/)
|
97
|
+
raise "No jruby-jars gem found in rubygems.org!" unless jruby_jars_line
|
98
|
+
self.jruby_version = /\((.*?)\)/.match(jruby_jars_line.to_s)[1]
|
99
|
+
debug "jRuby version from latest jruby-jars gem: #{jruby_version}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def debug(msg)
|
105
|
+
puts msg if ENV['DEBUG']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/jarbler.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'yaml'
|
6
|
+
require 'bundler'
|
7
|
+
require 'bundler/lockfile_parser'
|
8
|
+
|
9
|
+
require_relative "jarbler/version"
|
10
|
+
require_relative "jarbler/builder"
|
11
|
+
require_relative "jarbler/config"
|
12
|
+
|
13
|
+
|
14
|
+
module Jarbler
|
15
|
+
def self.run
|
16
|
+
puts "Jarbler release #{VERSION}, #{VERSION_DATE} ( https://github.com/rammpeter/jarbler )"
|
17
|
+
Builder.new.build_jar
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
Jarbler::Config.new.create_config_file
|
22
|
+
end
|
23
|
+
end
|
data/sig/jarbler.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jarbler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Ramm
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Pack a Rails application into an executable jar file
|
14
|
+
email:
|
15
|
+
- Peter@ramm-oberhermsdorf.de
|
16
|
+
executables:
|
17
|
+
- jarble
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".ruby-version"
|
22
|
+
- CHANGELOG.md
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- bin/jarble
|
28
|
+
- build_gem.sh
|
29
|
+
- jarbler.gemspec
|
30
|
+
- lib/jarbler.rb
|
31
|
+
- lib/jarbler/JarMain.java
|
32
|
+
- lib/jarbler/builder.rb
|
33
|
+
- lib/jarbler/config.rb
|
34
|
+
- lib/jarbler/version.rb
|
35
|
+
- sig/jarbler.rbs
|
36
|
+
homepage: https://github.com/rammpeter/jarbler
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
homepage_uri: https://github.com/rammpeter/jarbler
|
41
|
+
source_code_uri: https://github.com/rammpeter/jarbler
|
42
|
+
changelog_uri: https://github.com/rammpeter/jarbler/CHANGELOG.md
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 2.6.0
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubygems_version: 3.4.12
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: Create jar file from Rails app
|
62
|
+
test_files: []
|