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