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.

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