# frozen_string_literal: true require 'digest' require 'net/http' def ruby_version File.foreach('rb/lib/selenium/webdriver/version.rb') do |line| return line.split('=').last.strip.tr("'", '') if line.include?('VERSION') end end def setup_gem_credentials gem_dir = File.join(Dir.home, '.gem') credentials = File.join(gem_dir, 'credentials') return if File.exist?(credentials) && File.read(credentials).include?(':rubygems_api_key:') token = ENV.fetch('GEM_HOST_API_KEY', nil) if token.nil? || token.empty? raise 'Missing RubyGems credentials: set GEM_HOST_API_KEY or configure ~/.gem/credentials' end FileUtils.mkdir_p(gem_dir) if File.exist?(credentials) File.open(credentials, 'a') { |f| f.puts(":rubygems_api_key: #{token}") } else File.write(credentials, ":rubygems_api_key: #{token}\n") end File.chmod(0o600, credentials) end desc 'Generate Ruby gems' task :build do |_task, arguments| args = arguments.to_a webdriver = args.delete('webdriver') devtools = args.delete('devtools') Bazel.execute('build', args, '//rb:selenium-webdriver') if webdriver || !devtools Bazel.execute('build', args, '//rb:selenium-devtools') if devtools || !webdriver end desc 'Update generated Ruby files for local development' task :local_dev do puts 'installing ruby, this may take a minute' Bazel.execute('build', [], '@bundle//:bundle') Rake::Task['rb:build'].invoke Rake::Task['grid'].invoke # A command like this is required to move ruby binary into working directory Bazel.execute('build', %w[--test_arg --dry-run], '@bundle//bin:rubocop') end desc 'Validate Ruby release credentials' task :check_credentials do |_task, arguments| nightly = arguments.to_a.include?('nightly') next if nightly credentials = File.join(Dir.home, '.gem', 'credentials') has_file = File.exist?(credentials) && File.read(credentials).include?(':rubygems_api_key:') has_env = ENV.fetch('GEM_HOST_API_KEY', nil) && !ENV['GEM_HOST_API_KEY'].empty? raise 'Missing RubyGems credentials: set GEM_HOST_API_KEY or configure ~/.gem/credentials' unless has_file || has_env end desc 'Push Ruby gems to rubygems' task :release do |_task, arguments| nightly = arguments.to_a.include?('nightly') Rake::Task['rb:check_credentials'].invoke(*arguments.to_a) if nightly if ENV.fetch('GITHUB_TOKEN', '').empty? raise 'Missing GitHub Packages token: set GITHUB_TOKEN for nightly Ruby publish' end ENV['GEM_HOST_API_KEY'] = "Bearer #{ENV.fetch('GITHUB_TOKEN', nil)}" puts 'Bumping Ruby nightly version...' Bazel.execute('run', [], '//rb:selenium-webdriver-bump-nightly-version') puts 'Releasing nightly WebDriver gem...' Bazel.execute('run', ['--config=release'], '//rb:selenium-webdriver-release-nightly') else setup_gem_credentials patch_release = ruby_version.split('.').fetch(2, '0').to_i.positive? puts 'Releasing Ruby gems...' Bazel.execute('run', ['--config=release'], '//rb:selenium-webdriver-release') Bazel.execute('run', ['--config=release'], '//rb:selenium-devtools-release') unless patch_release end end desc 'Verify Ruby packages are published on RubyGems' task :verify do patch_release = ruby_version.split('.').fetch(2, '0').to_i.positive? SeleniumRake.verify_package_published("https://rubygems.org/api/v2/rubygems/selenium-webdriver/versions/#{ruby_version}.json") unless patch_release SeleniumRake.verify_package_published("https://rubygems.org/api/v2/rubygems/selenium-devtools/versions/#{ruby_version}.json") end end desc 'Generate and stage Ruby documentation' task :docs do |_task, arguments| if ruby_version.include?('nightly') && !arguments.to_a.include?('force') abort('Aborting documentation update: nightly versions should not update docs.') end Rake::Task['rb:docs_generate'].invoke FileUtils.mkdir_p('build/docs/api') FileUtils.cp_r('bazel-bin/rb/docs.sh.runfiles/_main/docs/api/rb/.', 'build/docs/api/rb') end desc 'Generate Ruby documentation without staging' task :docs_generate do puts 'Generating Ruby documentation' FileUtils.rm_rf('build/docs/api/rb/') Bazel.execute('run', [], '//rb:docs') end desc 'Install Ruby gem locally' task :install do Bazel.execute('build', [], '//rb:selenium-webdriver') Dir.glob('bazel-bin/rb/selenium-webdriver-*.gem').each do |gem| sh 'gem', 'install', gem end end desc 'Update Ruby changelog' task :changelogs do header = "#{ruby_version} (#{Time.now.strftime('%Y-%m-%d')})\n=========================" SeleniumRake.update_changelog(ruby_version, 'rb', 'rb/lib/', 'rb/CHANGES', header) end desc 'Update Ruby version' task :version, [:version] do |_task, arguments| old_version = ruby_version new_version = SeleniumRake.updated_version(old_version, arguments[:version], '.nightly') puts "Updating Ruby from #{old_version} to #{new_version}" file = 'rb/lib/selenium/webdriver/version.rb' text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } end desc 'Format Ruby code with rubocop (safe auto-correct only)' task :format do puts ' Running rubocop (safe auto-correct)...' Bazel.execute('run', ['--', '-a', '--fail-level', 'F'], '//rb:rubocop') end desc 'Run Ruby linters (rubocop, steep, docs)' task :lint do puts ' Running rubocop...' Bazel.execute('run', ['--', '-a'], '//rb:rubocop') puts ' Running steep type checker...' Bazel.execute('run', [], '//rb:steep') Rake::Task['rb:docs_generate'].invoke end desc 'Sync gem checksums from Gemfile.lock to MODULE.bazel (use force to re-download all)' task :pin, [:force] do |_task, arguments| gemfile_lock = 'rb/Gemfile.lock' module_bazel = 'MODULE.bazel' force = arguments[:force] == 'force' lock_content = File.read(gemfile_lock) gem_section = lock_content[/GEM\n\s+remote:.*?\n\s+specs:\n(.*?)(?=\n[A-Z]|\Z)/m, 1] gems = gem_section.scan(/^ ([a-zA-Z0-9_-]+) \(([^)]+)\)$/) needed_gems = gems.map { |name, version| "#{name}-#{version}" } # Parse existing checksums from MODULE.bazel module_content = File.read(module_bazel) existing = module_content.scan(/"([^"]+)":\s*"([a-f0-9]{64})"/).to_h # Keep existing checksums for gems still in Gemfile.lock (unless force) checksums = force ? {} : existing.slice(*needed_gems) to_download = needed_gems - checksums.keys puts "Found #{gems.size} gems: #{checksums.size} cached, #{to_download.size} to download..." failed = [] to_download.each do |key| uri = URI("https://rubygems.org/gems/#{key}.gem") response = nil 5.times do response = Net::HTTP.get_response(uri) break unless response.is_a?(Net::HTTPRedirection) uri = URI(response['location']) end unless response.is_a?(Net::HTTPSuccess) puts " #{key}: failed (HTTP #{response.code})" failed << key next end sha = Digest::SHA256.hexdigest(response.body) checksums[key] = sha puts " #{key}: #{sha[0, 16]}..." rescue StandardError => e puts " #{key}: failed (#{e.message})" failed << key end raise "Failed to download checksums for: #{failed.join(', ')}" if failed.any? checksums_lines = checksums.keys.sort.map { |k| " \"#{k}\": \"#{checksums[k]}\"," } formatted = " gem_checksums = {\n#{checksums_lines.join("\n")}\n }," new_content = module_content.sub(/ gem_checksums = \{[^}]+\},/m, formatted) File.write(module_bazel, new_content) end desc 'Update Ruby dependencies and sync checksums to MODULE.bazel' task :update do puts 'updating and pinning gem versions' Bazel.execute('run', [], '//rb:bundle-update') Bazel.execute('run', [], '//rb:rbs-update') Rake::Task['rb:pin'].invoke end