Locking It Down
with Ruby
Danielle Adams / GORUCO 2018
@adamzdanielle
theoatmeal.com
Locking It Down
with Ruby 🔒
(things I appreciate in regards to Ruby package management and dependency locking)
locking
In code bases, there are several dependencies and libraries used for both runtime and development. Locking freezes the dependency tree or parts of it to ensure a consistent build across all computers.
pessimistic locking
???
// "~> 4.3.0" -> "4.3.0"
shrinkwrap
yarn.lock
package-lock.json
A Brief History
0
(1995)
9 A.R.
(2004)
14 A.R.
(2009)
The Good Parts
❤️
global and application dependencies are installed with ease
$ gem install rails
$ rails new my-awesome-rails-app
[--skip-bundle && bundle install]
gem "rspec"
$ bundle exec rspec
#itworks
bundler resolves dependency conflicts
bundler.io
# Gemfile
source "https://rubygems.org"
ruby "2.5.1"
gem "concurrent-ruby",
"<= 0.8.0" # latest is 1.0.5
gem "i18n" # latest is 1.0.1
# Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (0.8.0)
ref (~> 1.0, >= 1.0.5)
i18n (0.8.6)
ref (1.0.5)
PLATFORMS
ruby
DEPENDENCIES
concurrent-ruby (<= 0.8.0)
i18n
RUBY VERSION
ruby 2.5.1p57
BUNDLED WITH
1.16.1
👍
i18n (0.8.6)
# Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (0.8.0)
ref (~> 1.0, >= 1.0.5)
i18n (0.8.6)
ref (1.0.5)
PLATFORMS
ruby
DEPENDENCIES
concurrent-ruby (<= 0.8.0)
i18n
RUBY VERSION
ruby 2.5.1p57
BUNDLED WITH
1.16.1
# Gemfile.lock
GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (0.8.0)
ref (~> 1.0, >= 1.0.5)
i18n (0.8.6)
ref (1.0.5)
PLATFORMS
ruby
DEPENDENCIES
concurrent-ruby (<= 0.8.0)
i18n
RUBY VERSION
ruby 2.5.1p57
BUNDLED WITH
1.16.1
packages are easy to develop and reliably available
$ gem build my-awesome-gem.gemspec
📦
gem push my-awesome-gem-0.0.0.gem
Uploaded
to AWS
$
$ gem install my-awesome-gem
gem "my-awesome-gem"
$ bundle
executables are built using Ruby
$ gem install colorize
# rubygems/bin/gem
args = ARGV.clone
begin
Gem::GemRunner.new.run args
rescue Gem::SystemExitException => e
exit e.exit_code
end
# rubygems/lib/rubygems/gem_runner.rb
def run args
build_args = extract_build_args args
do_configuration args
cmd = @command_manager_class.instance
cmd.command_names.each do |command_name|
config_args = Gem.configuration[command_name]
config_args = case config_args
when String
config_args.split ' '
else
Array(config_args)
end
Gem::Command.add_specific_extra_args command_name, config_args
end
cmd.run Gem.configuration.args, build_args
end
# rubygems/lib/rubygems/gem_runner.rb
def run args
build_args = extract_build_args args
do_configuration args
cmd = @command_manager_class.instance
cmd.command_names.each do |command_name|
config_args = Gem.configuration[command_name]
config_args = case config_args
when String
config_args.split ' '
else
Array(config_args)
end
Gem::Command.add_specific_extra_args command_name, config_args
end
cmd.run Gem.configuration.args, build_args
end
cmd = @command_manager_class.instance
cmd.run Gem.configuration.args, build_args
# rubygems/lib/rubygems/commands/install_command.rb
def execute
if options.include? :gemdeps then
install_from_gemdeps
return # not reached
end
@installed_specs = []
ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
check_install_dir
check_version
load_hooks
exit_code = install_gems
show_installed
terminate_interaction exit_code
end
# rubygems/lib/rubygems/commands/install_command.rb
def execute
if options.include? :gemdeps then
install_from_gemdeps
return # not reached
end
@installed_specs = []
ENV.delete 'GEM_PATH' if options[:install_dir].nil? and RUBY_VERSION > '1.9'
check_install_dir
check_version
load_hooks
exit_code = install_gems
show_installed
terminate_interaction exit_code
end
exit_code = install_gems
terminate_interaction exit_code
$ bundle install
def gemfile(install = false, options = {}, &gemfile)
require "bundler"
opts = options.dup
ui = opts.delete(:ui) { Bundler::UI::Shell.new }
(...)
Bundler.ui = ui if install
if install || missing_specs.call
installer = Bundler::Installer.install(Bundler.root,
definition, :system => true, :inline => true)
installer.post_install_messages.each do |name, message|
Bundler.ui.info "Post-install message from #{name}:\n#{message}"
end
end
runtime = Bundler::Runtime.new(nil, definition)
runtime.setup.require
ensure
bundler_module = class << Bundler; self; end
bundler_module.send(:define_method, :root, old_root) if old_root
end
def gemfile(install = false, options = {}, &gemfile)
require "bundler"
opts = options.dup
ui = opts.delete(:ui) { Bundler::UI::Shell.new }
(...)
Bundler.ui = ui if install
if install || missing_specs.call
installer = Bundler::Installer.install(Bundler.root,
definition, :system => true, :inline => true)
installer.post_install_messages.each do |name, message|
Bundler.ui.info "Post-install message from #{name}:\n#{message}"
end
end
runtime = Bundler::Runtime.new(nil, definition)
runtime.setup.require
ensure
bundler_module = class << Bundler; self; end
bundler_module.send(:define_method, :root, old_root) if old_root
end
def gemfile(install = false, options = {}, &gemfile)
installer = Bundler::Installer.install(Bundler.root, definition,
:system => true, :inline => true)
In conclusion,
it all works well
but there's always work to be done
thank you 🎉
tweet me @adamzdanielle
slides.com/danielleadams/locking-it-down
Locking It Down with Ruby
By Danielle Adams
Locking It Down with Ruby
- 1,407