Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Upgrading GitHub from Ruby 2.6 to 2.7

Upgrading GitHub from Ruby 2.6 to 2.7

It's no secret that the upgrade to Ruby 2.7 is difficult — fixing the keyword argument, URI, and other deprecation warnings can feel overwhelming, tedious, and never ending. We experienced this first-hand at GitHub; we fixed over 11k+ warnings, sent patches to 15+ gems, upgraded 30+ gems, and replaced abandoned gems. In this talk we’ll look at our custom monkey patch for capturing warnings, how we divided work among teams, and the keys to a successful Ruby 2.7 upgrade. We’ll explore why upgrading is important and take a dive into Ruby 2.7’s notable performance improvements.

Avatar for Eileen M. Uchitelle

Eileen M. Uchitelle

November 19, 2020
Tweet

More Decks by Eileen M. Uchitelle

Other Decks in Programming

Transcript

  1. a

  2. a

  3. MyClass.new({ part_1: "I work fine", part_2: "in Ruby 2.6" })

    => "I work fine in Ruby 2.6" DEPRECATION: Separation of arguments
  4. MyClass.new({ part_1: "I throw warnings", part_2: "in 2.7" }) =>

    "I throw warnings in 2.7" example.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call example.rb:2: warning: The called method `initialize' is defined here DEPRECATION: Separation of arguments
  5. DEPRECATION: Separation of arguments MyClass.new({ part_1: "I throw warnings", part_2:

    "in 2.7" }) => "I throw warnings in 2.7" example.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call example.rb:2: warning: The called method `initialize' is defined here The Caller
  6. DEPRECATION: Separation of arguments MyClass.new({ part_1: "I throw warnings", part_2:

    "in 2.7" }) => "I throw warnings in 2.7" example.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call example.rb:2: warning: The called method `initialize' is defined here The Definition
  7. MyClass.new({ part_1: "I throw warnings", part_2: "in 2.7" }) MyClass.new(

    part_1: "I don't throw warnings", part_2: "in 2.7" ) => "I don't throw warnings in 2.7" DEPRECATION: Separation of arguments
  8. MyClass.new({ part_1: "I throw warnings", part_2: "in 2.7" }) MyClass.new(**{

    part_1: "I don't throw warnings", part_2: "in 2.7" }) => "I don't throw warnings in 2.7" DEPRECATION: Separation of arguments
  9. class MyClass def initialize(part_1:, part_2:) puts "#{part_1} #{part_2}" end end

    class AbstractClass def initialize(*args) MyClass.new(*args) end end DEPRECATION: Separation of arguments
  10. AbstractClass.new( part_1: "I throw warnings", part_2: "in 2.7" ) example.rb:9:

    warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call example.rb:2: warning: The called method `initialize' is defined here DEPRECATION: Separation of arguments
  11. class MyClass def initialize(part_1:, part_2:) puts "#{part_1} #{part_2}" end end

    class AbstractClass def initialize(**kwargs) MyClass.new(**kwargs) end end DEPRECATION: Separation of arguments
  12. MyClassJob.perform_later({ part_1: "I am a", part_2: "job with kwargs" })

    activejob/lib/active_job/execution.rb:48: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call app/jobs/my_job_class_job.rb:2: warning: The called method `perform' is defined here DEPRECATION: Separation of arguments
  13. # config/ruby-version RUBY_NEXT_SHA = "d1ba554" RUBY_SHA = "dcc231c" if ENV["RUBY_NEXT"]

    print RUBY_NEXT_SHA else print RUBY_SHA end UPGRADING: Dual-booting
  14. module Warning def self.warn(message) STDERR.print(message) if ENV["RAISE_ON_WARNINGS"] raise message end

    if ENV["DEBUG_WARNINGS"] STDERR.puts caller end end end UPGRADING: Warning monkey patch
  15. module Warning def self.warn(message) STDERR.print(message) if ENV["RAISE_ON_WARNINGS"] raise message end

    if ENV["DEBUG_WARNINGS"] STDERR.puts caller end end end UPGRADING: Warning monkey patch
  16. module Warning def self.warn(message) STDERR.print(message) if ENV["RAISE_ON_WARNINGS"] raise message end

    if ENV["DEBUG_WARNINGS"] STDERR.puts caller end end end UPGRADING: Warning monkey patch
  17. module Warning def self.warn(message) STDERR.print(message) if ENV["RAISE_ON_WARNINGS"] raise message end

    if ENV["DEBUG_WARNINGS"] STDERR.puts caller end end end UPGRADING: Warning monkey patch
  18. activejob/lib/active_job/execution.rb:48: warning: Using the last argument as keyword parameters is

    deprecated; maybe ** should be added to the call activejob/lib/active_job/execution.rb:48:in `block in perform_now' ... test/integration/a_test.rb:6:in `block in test_something' activesupport/lib/active_support/testing/assertions.rb:34:in `assert_nothing_raised' activejob/lib/active_job/test_helper.rb:591:in `perform_enqueued_jobs' test/integration/my_class_job_test.rb:5:in `test_my_class_job' ... app/jobs/my_job_class_job.rb:2: warning: The called method `perform' is defined here UPGRADING: Warning monkey patch
  19. def test_my_class_job perform_enqueued_jobs do MyClassJob.perform_later({ part_1: "I am a", part_2:

    "job with kwargs" }) end ... end UPGRADING: Warning monkey patch
  20. module Warning def self.warn(message) line = caller_locations.find do |location| location.path.end_with?("_test.rb")

    end WarningsCollector.instance << [message.chomp, line.path] STDERR.print(message) ... UPGRADING: Warning monkey patch
  21. class WarningsCollector < ParallelCollector def process path = File.join("/tmp", "warnings.txt")

    File.open(path, "a") do |f| @data.each do |message, origin| f.puts [message, origin].join("*^.^*") end end script = File.absolute_path( "../../../script/process-warnings", __FILE__) system(script, "/tmp") end end UPGRADING: Warning monkey patch
  22. class WarningsCollector < ParallelCollector def process path = File.join("/tmp", "warnings.txt")

    File.open(path, "a") do |f| @data.each do |message, filepath| f.puts [message, filepath].join("*^.^*") end end script = File.absolute_path( "../../../script/process-warnings", __FILE__) system(script, "/tmp") end end UPGRADING: Warning monkey patch
  23. class WarningsCollector < ParallelCollector def process path = File.join("/tmp", "warnings.txt")

    File.open(path, "a") do |f| @data.each do |message, filepath| f.puts [message, filepath].join("*^.^*") end end script = File.absolute_path( "../../../script/process-warnings", __FILE__) system(script, "/tmp") end end UPGRADING: Warning monkey patch
  24. class WarningsCollector < ParallelCollector def process path = File.join("/tmp", "warnings.txt")

    File.open(path, "a") do |f| @data.each do |message, filepath| f.puts [message, filepath].join("*^.^*") end end script = File.absolute_path( "../../../script/process-warnings", __FILE__) system(script, "/tmp") end end UPGRADING: Warning monkey patch
  25. warnings = {} Dir["tmp/warning*.txt"].each do |file| File.read(file).split("\n").each do |filepath| message,

    filepath = line.split("*^.^*") warnings[message] ||= Message.new(message) warnings[message].paths << filepath if filepath end end warnings.values.each do |warning| warning.owner ||= CODEOWNERS.for(source_file).keys.first.to_s end UPGRADING: Warning monkey patch
  26. - [ ] `test/lib/platform/mutations/ update_mobile_push_notification_schedules_test.rb` - **warnings** - Line 118:

    warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call - [ ] `test/test_helpers/newsies/deliver_notifications_job_test_helper.rb` - **warnings** - Line 1282: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call - Line 1283: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call - **test suites that trigger these warnings** UPGRADING: Warning File Generation
  27. a

  28. class MyClass ... def my_method(arg, part_1:) end end obj =

    MyClass.new(part_1: "a", part_2: "b") obj.method(:my_method) => #<Method: MyClass#my_method> FEATURE: Method#inspect improvements
  29. class MyClass ... def my_method(arg, part_1:) end end obj =

    MyClass.new(part_1: "a", part_2: "b") obj.method(:my_method) => #<Method: MyClass#some_method(arg, part_1:) (irb):19> FEATURE: Method#inspect improvements
  30. module Warning def self.warn(message, category: nil) if category == :deprecated

    raise message else super end end end UPSTREAM FIX: Warning categories