appbundler 0.6.0 → 0.7.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/.gitignore +16 -16
- data/.rspec +2 -2
- data/.travis.yml +16 -12
- data/Gemfile +4 -4
- data/LICENSE.txt +202 -202
- data/README.md +70 -70
- data/Rakefile +1 -1
- data/appbundler.gemspec +27 -27
- data/bin/appbundler +13 -13
- data/lib/appbundler.rb +4 -4
- data/lib/appbundler/app.rb +194 -192
- data/lib/appbundler/cli.rb +113 -113
- data/lib/appbundler/version.rb +3 -3
- data/spec/appbundler/app_spec.rb +421 -421
- data/spec/fixtures/appbundler-example-app/.bundle/config +2 -2
- data/spec/fixtures/appbundler-example-app/Gemfile +2 -2
- data/spec/fixtures/appbundler-example-app/Gemfile.lock.unix +135 -135
- data/spec/fixtures/appbundler-example-app/Gemfile.lock.windows +172 -172
- data/spec/fixtures/appbundler-example-app/README.md +3 -3
- data/spec/fixtures/appbundler-example-app/appbundler-example-app.gemspec +22 -22
- data/spec/fixtures/appbundler-example-app/bin/app-binary-1 +2 -2
- data/spec/fixtures/appbundler-example-app/bin/app-binary-2 +2 -2
- data/spec/fixtures/appbundler-example-app/lib/example_app.rb +3 -3
- data/spec/spec_helper.rb +9 -9
- metadata +3 -3
data/README.md
CHANGED
@@ -1,70 +1,70 @@
|
|
1
|
-
# Appbundler
|
2
|
-
|
3
|
-
Appbundler reads a Gemfile.lock and generates code with
|
4
|
-
`gem "some-dep", "= VERSION"` statements to lock the app's dependencies
|
5
|
-
to the versions selected by bundler. This code is used in binstubs for
|
6
|
-
the application so that running (e.g.) `chef-client` on the command line
|
7
|
-
activates the locked dependencies for `chef` before running the command.
|
8
|
-
|
9
|
-
This provides the following benefits:
|
10
|
-
* The application loads faster because rubygems is not resolving
|
11
|
-
dependency constraints at runtime.
|
12
|
-
* The application runs with the same dependencies that it would if
|
13
|
-
bundler was used, so we can test applications (that will be installed
|
14
|
-
in an omnibus package) using the default bundler workflow.
|
15
|
-
* There's no need to `bundle exec` or patch the bundler runtime into the
|
16
|
-
app.
|
17
|
-
* The app can load gems not included in the Gemfile/gemspec. Our use
|
18
|
-
case for this is to load plugins (e.g., for knife and test kitchen).
|
19
|
-
* A user can use rvm and still use the application (see below).
|
20
|
-
* The application is protected from installation of incompatible
|
21
|
-
dependencies.
|
22
|
-
|
23
|
-
# Usage
|
24
|
-
|
25
|
-
Install via rubygems: `gem install appbundler` or clone this project and
|
26
|
-
bundle install:
|
27
|
-
|
28
|
-
```
|
29
|
-
git clone https://github.com/opscode/appbundler.git
|
30
|
-
cd appbundler
|
31
|
-
bundle install
|
32
|
-
```
|
33
|
-
|
34
|
-
Clone whatever project you want to appbundle somewhere else, and bundle
|
35
|
-
install it:
|
36
|
-
|
37
|
-
```
|
38
|
-
mkdir ~/oc
|
39
|
-
cd ~/oc
|
40
|
-
git clone https://github.com/opscode/chef.git
|
41
|
-
cd chef
|
42
|
-
bundle install
|
43
|
-
```
|
44
|
-
|
45
|
-
Create a bin directory where your bundled binstubs will live:
|
46
|
-
|
47
|
-
```
|
48
|
-
mkdir ~/appbundle-bin
|
49
|
-
# Add to your PATH if you like
|
50
|
-
```
|
51
|
-
|
52
|
-
Now you can app bundle your project (chef in our example):
|
53
|
-
|
54
|
-
```
|
55
|
-
bin/appbundler ~/oc/chef ~/appbundler-bin
|
56
|
-
```
|
57
|
-
|
58
|
-
Now you can run all of the app's executables with locked down deps:
|
59
|
-
|
60
|
-
```
|
61
|
-
~/appbunlder-bin/chef-client -v
|
62
|
-
```
|
63
|
-
|
64
|
-
|
65
|
-
# RVM
|
66
|
-
|
67
|
-
The generated binstubs explicitly disable rvm, so the above won't work
|
68
|
-
if you're using rvm. This is intentional, because our use case is for
|
69
|
-
omnibus applications where rvm's environment variables can break the
|
70
|
-
embedded application by making ruby look for gems in rvm's gem repo.
|
1
|
+
# Appbundler
|
2
|
+
|
3
|
+
Appbundler reads a Gemfile.lock and generates code with
|
4
|
+
`gem "some-dep", "= VERSION"` statements to lock the app's dependencies
|
5
|
+
to the versions selected by bundler. This code is used in binstubs for
|
6
|
+
the application so that running (e.g.) `chef-client` on the command line
|
7
|
+
activates the locked dependencies for `chef` before running the command.
|
8
|
+
|
9
|
+
This provides the following benefits:
|
10
|
+
* The application loads faster because rubygems is not resolving
|
11
|
+
dependency constraints at runtime.
|
12
|
+
* The application runs with the same dependencies that it would if
|
13
|
+
bundler was used, so we can test applications (that will be installed
|
14
|
+
in an omnibus package) using the default bundler workflow.
|
15
|
+
* There's no need to `bundle exec` or patch the bundler runtime into the
|
16
|
+
app.
|
17
|
+
* The app can load gems not included in the Gemfile/gemspec. Our use
|
18
|
+
case for this is to load plugins (e.g., for knife and test kitchen).
|
19
|
+
* A user can use rvm and still use the application (see below).
|
20
|
+
* The application is protected from installation of incompatible
|
21
|
+
dependencies.
|
22
|
+
|
23
|
+
# Usage
|
24
|
+
|
25
|
+
Install via rubygems: `gem install appbundler` or clone this project and
|
26
|
+
bundle install:
|
27
|
+
|
28
|
+
```
|
29
|
+
git clone https://github.com/opscode/appbundler.git
|
30
|
+
cd appbundler
|
31
|
+
bundle install
|
32
|
+
```
|
33
|
+
|
34
|
+
Clone whatever project you want to appbundle somewhere else, and bundle
|
35
|
+
install it:
|
36
|
+
|
37
|
+
```
|
38
|
+
mkdir ~/oc
|
39
|
+
cd ~/oc
|
40
|
+
git clone https://github.com/opscode/chef.git
|
41
|
+
cd chef
|
42
|
+
bundle install
|
43
|
+
```
|
44
|
+
|
45
|
+
Create a bin directory where your bundled binstubs will live:
|
46
|
+
|
47
|
+
```
|
48
|
+
mkdir ~/appbundle-bin
|
49
|
+
# Add to your PATH if you like
|
50
|
+
```
|
51
|
+
|
52
|
+
Now you can app bundle your project (chef in our example):
|
53
|
+
|
54
|
+
```
|
55
|
+
bin/appbundler ~/oc/chef ~/appbundler-bin
|
56
|
+
```
|
57
|
+
|
58
|
+
Now you can run all of the app's executables with locked down deps:
|
59
|
+
|
60
|
+
```
|
61
|
+
~/appbunlder-bin/chef-client -v
|
62
|
+
```
|
63
|
+
|
64
|
+
|
65
|
+
# RVM
|
66
|
+
|
67
|
+
The generated binstubs explicitly disable rvm, so the above won't work
|
68
|
+
if you're using rvm. This is intentional, because our use case is for
|
69
|
+
omnibus applications where rvm's environment variables can break the
|
70
|
+
embedded application by making ruby look for gems in rvm's gem repo.
|
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
1
|
+
require "bundler/gem_tasks"
|
data/appbundler.gemspec
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'appbundler/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "appbundler"
|
8
|
-
spec.version = Appbundler::VERSION
|
9
|
-
spec.authors = ["danielsdeleo"]
|
10
|
-
spec.email = ["dan@opscode.com"]
|
11
|
-
spec.description = %q{Extracts a dependency solution from bundler's Gemfile.lock to speed gem activation}
|
12
|
-
spec.summary = spec.description
|
13
|
-
spec.homepage = ""
|
14
|
-
spec.license = "Apache2"
|
15
|
-
|
16
|
-
spec.files = `git ls-files`.split($/)
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_development_dependency "rake"
|
22
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
-
spec.add_development_dependency "pry"
|
24
|
-
spec.add_development_dependency "mixlib-shellout", "~> 1.0"
|
25
|
-
|
26
|
-
spec.add_dependency "mixlib-cli", "~> 1.4"
|
27
|
-
end
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'appbundler/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "appbundler"
|
8
|
+
spec.version = Appbundler::VERSION
|
9
|
+
spec.authors = ["danielsdeleo"]
|
10
|
+
spec.email = ["dan@opscode.com"]
|
11
|
+
spec.description = %q{Extracts a dependency solution from bundler's Gemfile.lock to speed gem activation}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "Apache2"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
spec.add_development_dependency "pry"
|
24
|
+
spec.add_development_dependency "mixlib-shellout", "~> 1.0"
|
25
|
+
|
26
|
+
spec.add_dependency "mixlib-cli", "~> 1.4"
|
27
|
+
end
|
data/bin/appbundler
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
Kernel.trap(:INT) { exit 1 }
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'appbundler/cli'
|
7
|
-
rescue LoadError
|
8
|
-
$:.unshift File.expand_path("../../lib", __FILE__)
|
9
|
-
require 'appbundler/cli'
|
10
|
-
end
|
11
|
-
|
12
|
-
Appbundler::CLI.run(ARGV)
|
13
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
Kernel.trap(:INT) { exit 1 }
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'appbundler/cli'
|
7
|
+
rescue LoadError
|
8
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
9
|
+
require 'appbundler/cli'
|
10
|
+
end
|
11
|
+
|
12
|
+
Appbundler::CLI.run(ARGV)
|
13
|
+
|
data/lib/appbundler.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
2
|
-
module Appbundler
|
3
|
-
end
|
4
|
-
|
1
|
+
|
2
|
+
module Appbundler
|
3
|
+
end
|
4
|
+
|
data/lib/appbundler/app.rb
CHANGED
@@ -1,192 +1,194 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'pp'
|
4
|
-
|
5
|
-
module Appbundler
|
6
|
-
class App
|
7
|
-
|
8
|
-
BINSTUB_FILE_VERSION=1
|
9
|
-
|
10
|
-
attr_reader :app_root
|
11
|
-
attr_reader :target_bin_dir
|
12
|
-
|
13
|
-
def self.demo
|
14
|
-
demo = new("/Users/ddeleo/oc/chef")
|
15
|
-
|
16
|
-
knife = demo.executables.grep(/knife/).first
|
17
|
-
puts demo.binstub(knife)
|
18
|
-
end
|
19
|
-
|
20
|
-
def initialize(app_root, target_bin_dir)
|
21
|
-
@app_root = app_root
|
22
|
-
@target_bin_dir = target_bin_dir
|
23
|
-
end
|
24
|
-
|
25
|
-
# Copy over any .bundler and Gemfile.lock files to the target gem
|
26
|
-
# directory. This will let us run tests from under that directory.
|
27
|
-
def copy_bundler_env
|
28
|
-
gem_path = app_gemspec.gem_dir
|
29
|
-
FileUtils.install(gemfile_lock, gem_path, :mode => 0644)
|
30
|
-
if File.exist?(dot_bundle_dir) && File.directory?(dot_bundle_dir)
|
31
|
-
FileUtils.cp_r(dot_bundle_dir, gem_path)
|
32
|
-
FileUtils.chmod_R("ugo+rX", File.join(gem_path, ".bundle"))
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def write_executable_stubs
|
37
|
-
executables_to_create = executables.map do |real_executable_path|
|
38
|
-
basename = File.basename(real_executable_path)
|
39
|
-
stub_path = File.join(target_bin_dir, basename)
|
40
|
-
[real_executable_path, stub_path]
|
41
|
-
end
|
42
|
-
|
43
|
-
executables_to_create.each do |real_executable_path, stub_path|
|
44
|
-
File.open(stub_path, "wb", 0755) do |f|
|
45
|
-
f.write(binstub(real_executable_path))
|
46
|
-
end
|
47
|
-
if RUBY_PLATFORM =~ /mswin|mingw|windows/
|
48
|
-
batch_wrapper_path = "#{stub_path}.bat"
|
49
|
-
File.open(batch_wrapper_path, "wb", 0755) do |f|
|
50
|
-
f.write(batchfile_stub)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
executables_to_create
|
56
|
-
end
|
57
|
-
|
58
|
-
def name
|
59
|
-
File.basename(app_root)
|
60
|
-
end
|
61
|
-
|
62
|
-
def dot_bundle_dir
|
63
|
-
File.join(app_root, ".bundle")
|
64
|
-
end
|
65
|
-
|
66
|
-
def gemfile_lock
|
67
|
-
File.join(app_root, "Gemfile.lock")
|
68
|
-
end
|
69
|
-
|
70
|
-
def ruby
|
71
|
-
Gem.ruby
|
72
|
-
end
|
73
|
-
|
74
|
-
def batchfile_stub
|
75
|
-
ruby_relpath_windows = ruby_relative_path.gsub('/', '\\')
|
76
|
-
<<-E
|
77
|
-
@ECHO OFF
|
78
|
-
"%~dp0\\#{ruby_relpath_windows}" "%~dpn0" %*
|
79
|
-
E
|
80
|
-
end
|
81
|
-
|
82
|
-
# Relative path from #target_bin_dir to #ruby. This is used to
|
83
|
-
# generate batch files for windows in a way that the package can be
|
84
|
-
# installed in a custom location. On Unix we don't support custom
|
85
|
-
# install locations so this isn't needed.
|
86
|
-
def ruby_relative_path
|
87
|
-
ruby_pathname = Pathname.new(ruby)
|
88
|
-
bindir_pathname = Pathname.new(target_bin_dir)
|
89
|
-
ruby_pathname.relative_path_from(bindir_pathname).to_s
|
90
|
-
end
|
91
|
-
|
92
|
-
def shebang
|
93
|
-
"#!#{ruby}\n"
|
94
|
-
end
|
95
|
-
|
96
|
-
# A specially formatted comment that documents the format version of the
|
97
|
-
# binstub files we generate.
|
98
|
-
#
|
99
|
-
# This comment should be unusual enough that we can reliably (enough)
|
100
|
-
# detect whether a binstub was created by Appbundler and parse it to learn
|
101
|
-
# what version of the format it uses. If we ever need to support reading or
|
102
|
-
# mutating existing binstubs, we'll know what file version we're starting
|
103
|
-
# with.
|
104
|
-
def file_format_comment
|
105
|
-
"#--APP_BUNDLER_BINSTUB_FORMAT_VERSION=#{BINSTUB_FILE_VERSION}--\n"
|
106
|
-
end
|
107
|
-
|
108
|
-
# Ruby code (as a string) that clears GEM_HOME and GEM_PATH environment
|
109
|
-
# variables. In an omnibus context, this is important so users can use
|
110
|
-
# things like rvm without accidentally pointing the app at rvm's
|
111
|
-
# ruby and gems.
|
112
|
-
#
|
113
|
-
# Environment sanitization can be skipped by setting the
|
114
|
-
# APPBUNDLER_ALLOW_RVM environment variable to "true". This feature
|
115
|
-
# exists to make tests run correctly on travis.ci (which uses rvm).
|
116
|
-
def env_sanitizer
|
117
|
-
%Q{ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"}
|
118
|
-
end
|
119
|
-
|
120
|
-
def runtime_activate
|
121
|
-
@runtime_activate ||= begin
|
122
|
-
statements = runtime_dep_specs.map {|s| %Q|gem "#{s.name}", "= #{s.version}"|}
|
123
|
-
activate_code = ""
|
124
|
-
activate_code << env_sanitizer << "\n"
|
125
|
-
activate_code << statements.join("\n") << "\n"
|
126
|
-
activate_code
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def binstub(bin_file)
|
131
|
-
shebang + file_format_comment + runtime_activate + load_statement_for(bin_file)
|
132
|
-
end
|
133
|
-
|
134
|
-
def load_statement_for(bin_file)
|
135
|
-
name, version = app_spec.name, app_spec.version
|
136
|
-
bin_basename = File.basename(bin_file)
|
137
|
-
<<-E
|
138
|
-
gem "#{name}", "= #{version}"
|
139
|
-
|
140
|
-
spec = Gem::Specification.find_by_name("#{name}", "= #{version}")
|
141
|
-
bin_file = spec.bin_file("#{bin_basename}")
|
142
|
-
|
143
|
-
Kernel.load(bin_file)
|
144
|
-
E
|
145
|
-
end
|
146
|
-
|
147
|
-
def executables
|
148
|
-
spec = app_gemspec
|
149
|
-
spec.executables.map {|e| spec.bin_file(e)}
|
150
|
-
end
|
151
|
-
|
152
|
-
def runtime_dep_specs
|
153
|
-
add_dependencies_from(app_spec)
|
154
|
-
end
|
155
|
-
|
156
|
-
def app_dependency_names
|
157
|
-
@app_dependency_names ||= app_spec.dependencies.map(&:name)
|
158
|
-
end
|
159
|
-
|
160
|
-
def app_gemspec
|
161
|
-
Gem::Specification.find_by_name(app_spec.name, app_spec.version)
|
162
|
-
end
|
163
|
-
|
164
|
-
def app_spec
|
165
|
-
spec_for(name)
|
166
|
-
end
|
167
|
-
|
168
|
-
def gemfile_lock_specs
|
169
|
-
parsed_gemfile_lock.specs
|
170
|
-
end
|
171
|
-
|
172
|
-
def parsed_gemfile_lock
|
173
|
-
@parsed_gemfile_lock ||= Bundler::LockfileParser.new(IO.read(gemfile_lock))
|
174
|
-
end
|
175
|
-
|
176
|
-
private
|
177
|
-
|
178
|
-
def add_dependencies_from(spec, collected_deps=[])
|
179
|
-
spec.dependencies.each do |dep|
|
180
|
-
next if collected_deps.any? {|s| s.name == dep.name }
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
end
|
1
|
+
require 'bundler'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Appbundler
|
6
|
+
class App
|
7
|
+
|
8
|
+
BINSTUB_FILE_VERSION=1
|
9
|
+
|
10
|
+
attr_reader :app_root
|
11
|
+
attr_reader :target_bin_dir
|
12
|
+
|
13
|
+
def self.demo
|
14
|
+
demo = new("/Users/ddeleo/oc/chef")
|
15
|
+
|
16
|
+
knife = demo.executables.grep(/knife/).first
|
17
|
+
puts demo.binstub(knife)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app_root, target_bin_dir)
|
21
|
+
@app_root = app_root
|
22
|
+
@target_bin_dir = target_bin_dir
|
23
|
+
end
|
24
|
+
|
25
|
+
# Copy over any .bundler and Gemfile.lock files to the target gem
|
26
|
+
# directory. This will let us run tests from under that directory.
|
27
|
+
def copy_bundler_env
|
28
|
+
gem_path = app_gemspec.gem_dir
|
29
|
+
FileUtils.install(gemfile_lock, gem_path, :mode => 0644)
|
30
|
+
if File.exist?(dot_bundle_dir) && File.directory?(dot_bundle_dir)
|
31
|
+
FileUtils.cp_r(dot_bundle_dir, gem_path)
|
32
|
+
FileUtils.chmod_R("ugo+rX", File.join(gem_path, ".bundle"))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_executable_stubs
|
37
|
+
executables_to_create = executables.map do |real_executable_path|
|
38
|
+
basename = File.basename(real_executable_path)
|
39
|
+
stub_path = File.join(target_bin_dir, basename)
|
40
|
+
[real_executable_path, stub_path]
|
41
|
+
end
|
42
|
+
|
43
|
+
executables_to_create.each do |real_executable_path, stub_path|
|
44
|
+
File.open(stub_path, "wb", 0755) do |f|
|
45
|
+
f.write(binstub(real_executable_path))
|
46
|
+
end
|
47
|
+
if RUBY_PLATFORM =~ /mswin|mingw|windows/
|
48
|
+
batch_wrapper_path = "#{stub_path}.bat"
|
49
|
+
File.open(batch_wrapper_path, "wb", 0755) do |f|
|
50
|
+
f.write(batchfile_stub)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
executables_to_create
|
56
|
+
end
|
57
|
+
|
58
|
+
def name
|
59
|
+
File.basename(app_root)
|
60
|
+
end
|
61
|
+
|
62
|
+
def dot_bundle_dir
|
63
|
+
File.join(app_root, ".bundle")
|
64
|
+
end
|
65
|
+
|
66
|
+
def gemfile_lock
|
67
|
+
File.join(app_root, "Gemfile.lock")
|
68
|
+
end
|
69
|
+
|
70
|
+
def ruby
|
71
|
+
Gem.ruby
|
72
|
+
end
|
73
|
+
|
74
|
+
def batchfile_stub
|
75
|
+
ruby_relpath_windows = ruby_relative_path.gsub('/', '\\')
|
76
|
+
<<-E
|
77
|
+
@ECHO OFF
|
78
|
+
"%~dp0\\#{ruby_relpath_windows}" "%~dpn0" %*
|
79
|
+
E
|
80
|
+
end
|
81
|
+
|
82
|
+
# Relative path from #target_bin_dir to #ruby. This is used to
|
83
|
+
# generate batch files for windows in a way that the package can be
|
84
|
+
# installed in a custom location. On Unix we don't support custom
|
85
|
+
# install locations so this isn't needed.
|
86
|
+
def ruby_relative_path
|
87
|
+
ruby_pathname = Pathname.new(ruby)
|
88
|
+
bindir_pathname = Pathname.new(target_bin_dir)
|
89
|
+
ruby_pathname.relative_path_from(bindir_pathname).to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def shebang
|
93
|
+
"#!#{ruby}\n"
|
94
|
+
end
|
95
|
+
|
96
|
+
# A specially formatted comment that documents the format version of the
|
97
|
+
# binstub files we generate.
|
98
|
+
#
|
99
|
+
# This comment should be unusual enough that we can reliably (enough)
|
100
|
+
# detect whether a binstub was created by Appbundler and parse it to learn
|
101
|
+
# what version of the format it uses. If we ever need to support reading or
|
102
|
+
# mutating existing binstubs, we'll know what file version we're starting
|
103
|
+
# with.
|
104
|
+
def file_format_comment
|
105
|
+
"#--APP_BUNDLER_BINSTUB_FORMAT_VERSION=#{BINSTUB_FILE_VERSION}--\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Ruby code (as a string) that clears GEM_HOME and GEM_PATH environment
|
109
|
+
# variables. In an omnibus context, this is important so users can use
|
110
|
+
# things like rvm without accidentally pointing the app at rvm's
|
111
|
+
# ruby and gems.
|
112
|
+
#
|
113
|
+
# Environment sanitization can be skipped by setting the
|
114
|
+
# APPBUNDLER_ALLOW_RVM environment variable to "true". This feature
|
115
|
+
# exists to make tests run correctly on travis.ci (which uses rvm).
|
116
|
+
def env_sanitizer
|
117
|
+
%Q{ENV["GEM_HOME"] = ENV["GEM_PATH"] = nil unless ENV["APPBUNDLER_ALLOW_RVM"] == "true"}
|
118
|
+
end
|
119
|
+
|
120
|
+
def runtime_activate
|
121
|
+
@runtime_activate ||= begin
|
122
|
+
statements = runtime_dep_specs.map {|s| %Q|gem "#{s.name}", "= #{s.version}"|}
|
123
|
+
activate_code = ""
|
124
|
+
activate_code << env_sanitizer << "\n"
|
125
|
+
activate_code << statements.join("\n") << "\n"
|
126
|
+
activate_code
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def binstub(bin_file)
|
131
|
+
shebang + file_format_comment + runtime_activate + load_statement_for(bin_file)
|
132
|
+
end
|
133
|
+
|
134
|
+
def load_statement_for(bin_file)
|
135
|
+
name, version = app_spec.name, app_spec.version
|
136
|
+
bin_basename = File.basename(bin_file)
|
137
|
+
<<-E
|
138
|
+
gem "#{name}", "= #{version}"
|
139
|
+
|
140
|
+
spec = Gem::Specification.find_by_name("#{name}", "= #{version}")
|
141
|
+
bin_file = spec.bin_file("#{bin_basename}")
|
142
|
+
|
143
|
+
Kernel.load(bin_file)
|
144
|
+
E
|
145
|
+
end
|
146
|
+
|
147
|
+
def executables
|
148
|
+
spec = app_gemspec
|
149
|
+
spec.executables.map {|e| spec.bin_file(e)}
|
150
|
+
end
|
151
|
+
|
152
|
+
def runtime_dep_specs
|
153
|
+
add_dependencies_from(app_spec)
|
154
|
+
end
|
155
|
+
|
156
|
+
def app_dependency_names
|
157
|
+
@app_dependency_names ||= app_spec.dependencies.map(&:name)
|
158
|
+
end
|
159
|
+
|
160
|
+
def app_gemspec
|
161
|
+
Gem::Specification.find_by_name(app_spec.name, app_spec.version)
|
162
|
+
end
|
163
|
+
|
164
|
+
def app_spec
|
165
|
+
spec_for(name)
|
166
|
+
end
|
167
|
+
|
168
|
+
def gemfile_lock_specs
|
169
|
+
parsed_gemfile_lock.specs
|
170
|
+
end
|
171
|
+
|
172
|
+
def parsed_gemfile_lock
|
173
|
+
@parsed_gemfile_lock ||= Bundler::LockfileParser.new(IO.read(gemfile_lock))
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def add_dependencies_from(spec, collected_deps=[])
|
179
|
+
spec.dependencies.each do |dep|
|
180
|
+
next if collected_deps.any? {|s| s.name == dep.name }
|
181
|
+
# a bundler dep will not get pinned in Gemfile.lock
|
182
|
+
next if dep.name == "bundler"
|
183
|
+
next_spec = spec_for(dep.name)
|
184
|
+
collected_deps << next_spec
|
185
|
+
add_dependencies_from(next_spec, collected_deps)
|
186
|
+
end
|
187
|
+
collected_deps
|
188
|
+
end
|
189
|
+
|
190
|
+
def spec_for(dep_name)
|
191
|
+
gemfile_lock_specs.find {|s| s.name == dep_name } or raise "No spec #{dep_name}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|