# frozen_string_literal: true require 'English' $LOAD_PATH.unshift File.expand_path('.') require 'rake' require 'net/telnet' require 'stringio' require 'fileutils' require 'open-uri' require 'git' require 'find' Rake.application.instance_variable_set(:@name, 'go') orig_verbose = verbose verbose(false) # The CrazyFun build grammar. There's no magic here, just ruby require 'rake_tasks/crazy_fun/main' require 'rake_tasks/selenium_rake/detonating_handler' require 'rake_tasks/selenium_rake/crazy_fun' # The CrazyFun builders - Most of these are either partially or fully obsolete # Note the order here is important - The top 2 are used in inheritance chains require 'rake_tasks/crazy_fun/mappings/file_copy_hack' require 'rake_tasks/crazy_fun/mappings/tasks' require 'rake_tasks/crazy_fun/mappings/rake_mappings' # Location of all new (non-CrazyFun) methods require 'rake_tasks/selenium_rake/browsers' require 'rake_tasks/selenium_rake/checks' require 'rake_tasks/selenium_rake/cpp_formatter' require 'rake_tasks/selenium_rake/ie_generator' require 'rake_tasks/selenium_rake/java_formatter' require 'rake_tasks/selenium_rake/type_definitions_generator' # Our modifications to the Rake / Bazel libraries require 'rake/task' require 'rake_tasks/rake/task' require 'rake_tasks/rake/dsl' require 'rake_tasks/bazel/task' # These are the final items mixed into the global NS # These need moving into correct namespaces, and not be globally included require 'rake_tasks/bazel' require 'rake_tasks/python' $DEBUG = orig_verbose != Rake::FileUtilsExt::DEFAULT $DEBUG = true if ENV['debug'] == 'true' verbose($DEBUG) @git = Git.open(__dir__) def java_version File.foreach('java/version.bzl') do |line| return line.split('=').last.strip.tr('"', '') if line.include?('SE_VERSION') end end # The build system used by webdriver is layered on top of rake, and we call it # "crazy fun" for no readily apparent reason. # First off, create a new CrazyFun object. crazy_fun = SeleniumRake::CrazyFun.new # Secondly, we add the handlers, which are responsible for turning a build # rule into a (series of) rake tasks. For example if we're looking at a file # in subdirectory "subdir" contains the line: # # java_library(:name => "example", :srcs => ["foo.java"]) # # we would generate a rake target of "//subdir:example" which would generate # a Java JAR at "build/subdir/example.jar". # # If crazy fun doesn't know how to handle a particular output type ("java_library" # in the example above) then it will throw an exception, stopping the build CrazyFun::Mappings::RakeMappings.new.add_all(crazy_fun) # Finally, find every file named "build.desc" in the project, and generate # rake tasks from them. These tasks are normal rake tasks, and can be invoked # from rake. # FIXME: the rules for the targets were removed and build files won't load # crazy_fun.create_tasks(Dir['**/build.desc']) # If it looks like a bazel target, build it with bazel rule(%r{//.*}) do |task| task.out = Bazel.execute('build', %w[], task.name) end # Spoof tasks to get CI working with bazel task '//java/test/org/openqa/selenium/environment/webserver:webserver:uber' => [ '//java/test/org/openqa/selenium/environment:webserver' ] # Java targets required for release. These should all be java_export targets. # Generated from: bazel query 'kind(maven_publish, set(//java/... //third_party/...))' | sort JAVA_RELEASE_TARGETS = %w[ //java/src/org/openqa/selenium/chrome:chrome.publish //java/src/org/openqa/selenium/chromium:chromium.publish //java/src/org/openqa/selenium/devtools/v131:v131.publish //java/src/org/openqa/selenium/devtools/v132:v132.publish //java/src/org/openqa/selenium/devtools/v133:v133.publish //java/src/org/openqa/selenium/devtools/v85:v85.publish //java/src/org/openqa/selenium/edge:edge.publish //java/src/org/openqa/selenium/firefox:firefox.publish //java/src/org/openqa/selenium/grid/sessionmap/jdbc:jdbc.publish //java/src/org/openqa/selenium/grid/sessionmap/redis:redis.publish //java/src/org/openqa/selenium/grid:bom-dependencies.publish //java/src/org/openqa/selenium/grid:bom.publish //java/src/org/openqa/selenium/grid:grid.publish //java/src/org/openqa/selenium/ie:ie.publish //java/src/org/openqa/selenium/json:json.publish //java/src/org/openqa/selenium/manager:manager.publish //java/src/org/openqa/selenium/os:os.publish //java/src/org/openqa/selenium/remote/http:http.publish //java/src/org/openqa/selenium/remote:remote.publish //java/src/org/openqa/selenium/safari:safari.publish //java/src/org/openqa/selenium/support:support.publish //java/src/org/openqa/selenium:client-combined.publish //java/src/org/openqa/selenium:core.publish ].freeze # Notice that because we're using rake, anything you can do in a normal rake # build can also be done here. For example, here we set the default task task default: [:grid] task all: [ :'selenium-java', '//java/test/org/openqa/selenium/environment:webserver' ] task tests: [ '//java/test/org/openqa/selenium/htmlunit:htmlunit', '//java/test/org/openqa/selenium/firefox:test-synthesized', '//java/test/org/openqa/selenium/ie:ie', '//java/test/org/openqa/selenium/chrome:chrome', '//java/test/org/openqa/selenium/edge:edge', '//java/test/org/openqa/selenium/support:small-tests', '//java/test/org/openqa/selenium/support:large-tests', '//java/test/org/openqa/selenium/remote:small-tests', '//java/test/org/openqa/selenium/remote/server/log:test', '//java/test/org/openqa/selenium/remote/server:small-tests' ] task chrome: ['//java/src/org/openqa/selenium/chrome'] task grid: [:'selenium-server-standalone'] task ie: ['//java/src/org/openqa/selenium/ie'] task firefox: ['//java/src/org/openqa/selenium/firefox'] task remote: %i[remote_server remote_client] task remote_client: ['//java/src/org/openqa/selenium/remote'] task remote_server: ['//java/src/org/openqa/selenium/remote/server'] task safari: ['//java/src/org/openqa/selenium/safari'] task selenium: ['//java/src/org/openqa/selenium:core'] task support: ['//java/src/org/openqa/selenium/support'] desc 'Build the standalone server' task 'selenium-server-standalone' => '//java/src/org/openqa/selenium/grid:executable-grid' task test_javascript: [ '//javascript/atoms:test-chrome:run', '//javascript/webdriver:test-chrome:run', '//javascript/selenium-atoms:test-chrome:run', '//javascript/selenium-core:test-chrome:run' ] task test_chrome: ['//java/test/org/openqa/selenium/chrome:chrome:run'] task test_edge: ['//java/test/org/openqa/selenium/edge:edge:run'] task test_chrome_atoms: [ '//javascript/atoms:test-chrome:run', '//javascript/chrome-driver:test-chrome:run', '//javascript/webdriver:test-chrome:run' ] task test_htmlunit: [ '//java/test/org/openqa/selenium/htmlunit:htmlunit:run' ] task test_grid: [ '//java/test/org/openqa/grid/common:common:run', '//java/test/org/openqa/grid:grid:run', '//java/test/org/openqa/grid/e2e:e2e:run', '//java/test/org/openqa/selenium/remote:remote-driver-grid-tests:run' ] task test_ie: [ '//cpp/iedriverserver:win32', '//cpp/iedriverserver:x64', '//java/test/org/openqa/selenium/ie:ie:run' ] task test_jobbie: [:test_ie] task test_firefox: ['//java/test/org/openqa/selenium/firefox:marionette:run'] task test_remote_server: [ '//java/test/org/openqa/selenium/remote/server:small-tests:run', '//java/test/org/openqa/selenium/remote/server/log:test:run' ] task test_remote: [ '//java/test/org/openqa/selenium/json:small-tests:run', '//java/test/org/openqa/selenium/remote:common-tests:run', '//java/test/org/openqa/selenium/remote:client-tests:run', '//java/test/org/openqa/selenium/remote:remote-driver-tests:run', :test_remote_server ] task test_safari: ['//java/test/org/openqa/selenium/safari:safari:run'] task test_support: [ '//java/test/org/openqa/selenium/support:small-tests:run', '//java/test/org/openqa/selenium/support:large-tests:run' ] task :test_java_webdriver do if SeleniumRake::Checks.windows? Rake::Task['test_ie'].invoke elsif SeleniumRake::Checks.chrome? Rake::Task['test_chrome'].invoke elsif SeleniumRake::Checks.edge? Rake::Task['test_edge'].invoke else Rake::Task['test_htmlunit'].invoke Rake::Task['test_firefox'].invoke Rake::Task['test_remote_server'].invoke end end task test_java: [ '//java/test/org/openqa/selenium/atoms:test:run', :test_java_small_tests, :test_support, :test_java_webdriver, :test_selenium, 'test_grid' ] task test_java_small_tests: [ '//java/test/org/openqa/selenium:small-tests:run', '//java/test/org/openqa/selenium/json:small-tests:run', '//java/test/org/openqa/selenium/support:small-tests:run', '//java/test/org/openqa/selenium/remote:common-tests:run', '//java/test/org/openqa/selenium/remote:client-tests:run', '//java/test/org/openqa/grid/selenium/node:node:run', '//java/test/org/openqa/grid/selenium/proxy:proxy:run', '//java/test/org/openqa/selenium/remote/server:small-tests:run', '//java/test/org/openqa/selenium/remote/server/log:test:run' ] task :test do if SeleniumRake::Checks.python? Rake::Task['test_py'].invoke else Rake::Task['test_javascript'].invoke Rake::Task['test_java'].invoke end end task test_py: [:py_prep_for_install_release, 'py:marionette_test'] task build: %i[all firefox remote selenium tests] desc 'Clean build artifacts.' task :clean do rm_rf 'build/' rm_rf 'java/build/' rm_rf 'dist/' end # Create a new IEGenerator instance ie_generator = SeleniumRake::IEGenerator.new # Generate a C++ Header file for mapping between magic numbers and #defines # in the C++ code. ie_generator.generate_type_mapping( name: 'ie_result_type_cpp', src: 'cpp/iedriver/result_types.txt', type: 'cpp', out: 'cpp/iedriver/IEReturnTypes.h' ) desc 'Generate Javadocs' task javadocs: %i[//java/src/org/openqa/selenium/grid:all-javadocs] do FileUtils.rm_rf('build/docs/api/java') FileUtils.mkdir_p('build/docs/api/java') out = 'bazel-bin/java/src/org/openqa/selenium/grid/all-javadocs.jar' cmd = %(cd build/docs/api/java && jar xf "../../../../#{out}" 2>&1) cmd = cmd.tr('/', '\\').tr(':', ';') if SeleniumRake::Checks.windows? raise 'could not unpack javadocs' unless system(cmd) File.open('build/docs/api/java/stylesheet.css', 'a') do |file| file.write(<<~STYLE /* Custom selenium-specific styling */ .blink { animation: 2s cubic-bezier(0.5, 0, 0.85, 0.85) infinite blink; } @keyframes blink { 50% { opacity: 0; } } STYLE ) end end file 'cpp/iedriver/sizzle.h' => ['//third_party/js/sizzle:sizzle:header'] do cp 'build/third_party/js/sizzle/sizzle.h', 'cpp/iedriver/sizzle.h' end task sizzle_header: ['cpp/iedriver/sizzle.h'] task ios_driver: [ '//javascript/atoms/fragments:get_visible_text:ios', '//javascript/atoms/fragments:click:ios', '//javascript/atoms/fragments:back:ios', '//javascript/atoms/fragments:forward:ios', '//javascript/atoms/fragments:submit:ios', '//javascript/atoms/fragments:xpath:ios', '//javascript/atoms/fragments:xpaths:ios', '//javascript/atoms/fragments:type:ios', '//javascript/atoms/fragments:get_attribute:ios', '//javascript/atoms/fragments:clear:ios', '//javascript/atoms/fragments:is_selected:ios', '//javascript/atoms/fragments:is_enabled:ios', '//javascript/atoms/fragments:is_shown:ios', '//javascript/atoms/fragments:stringify:ios', '//javascript/atoms/fragments:link_text:ios', '//javascript/atoms/fragments:link_texts:ios', '//javascript/atoms/fragments:partial_link_text:ios', '//javascript/atoms/fragments:partial_link_texts:ios', '//javascript/atoms/fragments:get_interactable_size:ios', '//javascript/atoms/fragments:scroll_into_view:ios', '//javascript/atoms/fragments:get_effective_style:ios', '//javascript/atoms/fragments:get_element_size:ios', '//javascript/webdriver/atoms/fragments:get_location_in_view:ios' ] # This task does not allow running RBE, to run stamped with RBE use # ./go java:package['--config=release'] desc 'Create stamped zipped assets for Java for uploading to GitHub' task :'java-release-zip' do Rake::Task['java:package'].invoke('--config=remote_release') end task 'release-java': %i[java-release-zip publish-maven] def read_m2_user_pass puts 'Maven environment variables not set, inspecting /.m2/settings.xml.' settings = File.read("#{Dir.home}/.m2/settings.xml") found_section = false settings.each_line do |line| if !found_section found_section = line.include? 'sonatype-nexus-staging' elsif line.include?('') ENV['MAVEN_USER'] = line[%r{(.*?)}, 1] elsif line.include?('') ENV['MAVEN_PASSWORD'] = line[%r{(.*?)}, 1] end break if ENV['MAVEN_PASSWORD'] && ENV['MAVEN_USER'] end end desc 'Publish all Java jars to Maven as stable release' task 'publish-maven' do Rake::Task['java:release'].invoke end desc 'Publish all Java jars to Maven as nightly release' task 'publish-maven-snapshot' do Rake::Task['java:release'].invoke('nightly') end desc 'Install jars to local m2 directory' task :'maven-install' do JAVA_RELEASE_TARGETS.each do |p| Bazel.execute('run', ['--stamp', '--define', "maven_repo=file://#{Dir.home}/.m2/repository", '--define', 'gpg_sign=false'], p) end end desc 'Build the selenium client jars' task 'selenium-java' => '//java/src/org/openqa/selenium:client-combined' desc 'Update AUTHORS file' task :authors do sh "(git log --use-mailmap --format='%aN <%aE>' ; cat .OLD_AUTHORS) | sort -uf > AUTHORS" end namespace :side do task atoms: [ '//javascript/atoms/fragments:find-element' ] do # TODO: move directly to IDE's directory once the repositories are merged mkdir_p 'build/javascript/atoms' atom = 'bazel-bin/javascript/atoms/fragments/find-element.js' name = File.basename(atom) puts "Generating #{atom} as #{name}" File.open(File.join(baseDir, name), 'w') do |f| f << "// GENERATED CODE - DO NOT EDIT\n" f << 'module.exports = ' f << File.read(atom).strip f << ";\n" end end end def node_version File.foreach('javascript/node/selenium-webdriver/package.json') do |line| return line.split(':').last.strip.tr('",', '') if line.include?('version') end end namespace :node do atom_list = %w[ //javascript/atoms/fragments:find-elements //javascript/atoms/fragments:is-displayed //javascript/webdriver/atoms:get-attribute ] task atoms: atom_list do base_dir = 'javascript/node/selenium-webdriver/lib/atoms' mkdir_p base_dir ['bazel-bin/javascript/atoms/fragments/is-displayed.js', 'bazel-bin/javascript/webdriver/atoms/get-attribute.js', 'bazel-bin/javascript/atoms/fragments/find-elements.js'].each do |atom| name = File.basename(atom) puts "Generating #{atom} as #{name}" File.open(File.join(base_dir, name), 'w') do |f| f << "// GENERATED CODE - DO NOT EDIT\n" f << 'module.exports = ' f << File.read(atom).strip f << ";\n" end end end desc 'Build Node npm package' task :build do |_task, arguments| args = arguments.to_a.compact Bazel.execute('build', args, '//javascript/node/selenium-webdriver') end task :'dry-run' do Bazel.execute('run', ['--stamp'], '//javascript/node/selenium-webdriver:selenium-webdriver.publish -- --dry-run=true') end desc 'Release Node npm package' task :release do |_task, arguments| args = arguments.to_a.compact nightly = args.delete('nightly') Rake::Task['node:version'].invoke('nightly') if nightly Bazel.execute('run', ['--config=release'], '//javascript/node/selenium-webdriver:selenium-webdriver.publish') end desc 'Release Node npm package' task deploy: :release desc 'Generate Node documentation' task :docs, [:skip_update] do |_task, arguments| FileUtils.rm_rf('build/docs/api/javascript/') begin sh 'npm run generate-docs --prefix javascript/node/selenium-webdriver || true', verbose: true rescue StandardError puts 'Ensure that npm is installed on your system' raise end update_gh_pages unless arguments[:skip_update] end desc 'Update JavaScript changelog' task :changelog do header = "## #{node_version}\n" update_changelog(node_version, 'javascript', 'javascript/node/selenium-webdriver/', 'javascript/node/selenium-webdriver/CHANGES.md', header) end desc 'Update Node version' task :version, [:version] do |_task, arguments| old_version = node_version nightly = "-nightly#{Time.now.strftime('%Y%m%d%H%M')}" new_version = updated_version(old_version, arguments[:version], nightly) ['javascript/node/selenium-webdriver/package.json', 'javascript/node/selenium-webdriver/BUILD.bazel'].each do |file| text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } end Rake::Task['node:changelog'].invoke unless new_version.include?(nightly) end end def python_version File.foreach('py/BUILD.bazel') do |line| return line.split('=').last.strip.tr('"', '') if line.include?('SE_VERSION') end end namespace :py do desc 'Build Python wheel and sdist with optional arguments' task :build do |_task, arguments| args = arguments.to_a.compact Bazel.execute('build', args, '//py:selenium-wheel') Bazel.execute('build', args, '//py:selenium-sdist') end desc 'Release Python wheel and sdist to pypi' task :release do |_task, arguments| args = arguments.to_a.compact nightly = args.delete('nightly') Rake::Task['py:version'].invoke('nightly') if nightly command = nightly ? '//py:selenium-release-nightly' : '//py:selenium-release' Bazel.execute('run', ['--config=release'], command) end desc 'generate and copy files required for local development' task :local_dev do Bazel.execute('build', [], '//py:selenium') Rake::Task['grid'].invoke FileUtils.rm_rf('py/selenium/webdriver/common/devtools/') FileUtils.cp_r('bazel-bin/py/selenium/webdriver/.', 'py/selenium/webdriver', remove_destination: true) end desc 'Update generated Python files for local development' task :clean do Bazel.execute('build', [], '//py:selenium') bazel_bin_path = 'bazel-bin/py/selenium/webdriver' lib_path = 'py/selenium/webdriver' dirs = %w[devtools linux mac windows] dirs.each { |dir| FileUtils.rm_rf("#{lib_path}/common/#{dir}") } Find.find(bazel_bin_path) do |path| if File.directory?(path) && dirs.any? { |dir| path.include?("common/#{dir}") } Find.prune next end next if File.directory?(path) target_file = File.join(lib_path, path.sub(%r{^#{bazel_bin_path}/}, '')) if File.exist?(target_file) puts "Removing target file: #{target_file}" FileUtils.rm(target_file) end end end desc 'Generate Python documentation' task :docs, [:skip_update] do |_task, arguments| FileUtils.rm_rf('build/docs/api/py/') FileUtils.rm_rf('build/docs/doctrees/') begin sh 'tox -c py/tox.ini -e docs', verbose: true rescue StandardError puts 'Ensure that tox is installed on your system' raise end update_gh_pages unless arguments[:skip_update] end desc 'Install Python wheel locally' task :install do Bazel.execute('build', [], '//py:selenium-wheel') begin sh 'pip install bazel-bin/py/selenium-*.whl' rescue StandardError puts 'Ensure that Python and pip are installed on your system' raise end end desc 'Update Python changelog' task :changelog do header = "Selenium #{python_version}" update_changelog(python_version, 'py', 'py/selenium/webdriver', 'py/CHANGES', header) end desc 'Update Python version' task :version, [:version] do |_task, arguments| old_version = python_version nightly = ".#{Time.now.strftime('%Y%m%d%H%M')}" new_version = updated_version(old_version, arguments[:version], nightly) ['py/pyproject.toml', 'py/BUILD.bazel', 'py/selenium/__init__.py', 'py/selenium/webdriver/__init__.py', 'py/docs/source/conf.py'].each do |file| text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } end old_short_version = old_version.split('.')[0..1].join('.') new_short_version = new_version.split('.')[0..1].join('.') text = File.read('py/docs/source/conf.py').gsub(old_short_version, new_short_version) File.open('py/docs/source/conf.py', 'w') { |f| f.puts text } Rake::Task['py:changelog'].invoke unless new_version.include?(nightly) end desc 'Update Python Syntax' task :lint do sh 'tox -c py/tox.ini -e linting' end namespace :test do desc 'Python unit tests' task :unit do Rake::Task['py:clean'].invoke Bazel.execute('test', ['--test_size_filters=small'], '//py/...') end %i[chrome edge firefox safari].each do |browser| desc "Python #{browser} tests" task browser do Rake::Task['py:clean'].invoke Bazel.execute('test', [], "//py:common-#{browser}") Bazel.execute('test', [], "//py:test-#{browser}") end end desc 'Python Remote tests with Firefox' task :remote do Rake::Task['py:clean'].invoke Bazel.execute('test', [], '//py:test-remote') end end namespace :test do desc 'Python unit tests' task :unit do Rake::Task['py:clean'].invoke Bazel.execute('test', ['--test_size_filters=small'], '//py/...') end %i[chrome edge firefox safari].each do |browser| desc "Python #{browser} tests" task browser do Rake::Task['py:clean'].invoke Bazel.execute('test', %w[--test_output all], "//py:common-#{browser}") Bazel.execute('test', %w[--test_output all], "//py:test-#{browser}") end end end end 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 namespace :rb do desc 'Generate Ruby gems' task :build do |_task, arguments| args = arguments.to_a.compact 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 Bazel.execute('build', [], '@bundle//:bundle') Rake::Task['rb:build'].invoke Rake::Task['grid'].invoke end desc 'Push Ruby gems to rubygems' task :release do |_task, arguments| args = arguments.to_a.compact nightly = args.delete('nightly') if nightly Bazel.execute('run', [], '//rb:selenium-webdriver-bump-nightly-version') Bazel.execute('run', ['--config=release'], '//rb:selenium-webdriver-release-nightly') else Bazel.execute('run', ['--config=release'], '//rb:selenium-webdriver-release') Bazel.execute('run', ['--config=release'], '//rb:selenium-devtools-release') end end desc 'Generate Ruby documentation' task :docs, [:skip_update] do |_task, arguments| FileUtils.rm_rf('build/docs/api/rb/') Bazel.execute('run', [], '//rb:docs') FileUtils.mkdir_p('build/docs/api') FileUtils.cp_r('bazel-bin/rb/docs.sh.runfiles/_main/docs/api/rb/.', 'build/docs/api/rb') update_gh_pages unless arguments[:skip_update] end desc 'Update Ruby changelog' task :changelog do header = "#{ruby_version} (#{Time.now.strftime('%Y-%m-%d')})\n=========================" 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 = updated_version(old_version, arguments[:version], '.nightly') file = 'rb/lib/selenium/webdriver/version.rb' text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } Rake::Task['rb:changelog'].invoke unless new_version.include?('.nightly') sh 'cd rb && bundle --version && bundle update' end desc 'Update Ruby Syntax' task :lint do |_task, arguments| args = arguments.to_a.compact Bazel.execute('run', args, '//rb:lint') end end def dotnet_version File.foreach('dotnet/selenium-dotnet-version.bzl') do |line| return line.split('=').last.strip.tr('"', '') if line.include?('SE_VERSION') end end namespace :dotnet do desc 'Build nupkg files' task :build do |_task, arguments| args = arguments.to_a.compact Bazel.execute('build', args, '//dotnet:all') end desc 'Package .NET bindings into zipped assets and stage for release' task :package do |_task, arguments| args = arguments.to_a.compact.empty? ? ['--stamp'] : arguments.to_a.compact Rake::Task['dotnet:build'].invoke(*args) mkdir_p 'build/dist' FileUtils.rm_f(Dir.glob('build/dist/*dotnet*')) FileUtils.copy('bazel-bin/dotnet/release.zip', "build/dist/selenium-dotnet-#{dotnet_version}.zip") FileUtils.chmod(0o666, "build/dist/selenium-dotnet-#{dotnet_version}.zip") FileUtils.copy('bazel-bin/dotnet/strongnamed.zip', "build/dist/selenium-dotnet-strongnamed-#{dotnet_version}.zip") FileUtils.chmod(0o666, "build/dist/selenium-dotnet-strongnamed-#{dotnet_version}.zip") end desc 'Upload nupkg files to Nuget' task :release do |_task, arguments| args = arguments.to_a.compact nightly = args.delete('nightly') Rake::Task['dotnet:version'].invoke('nightly') if nightly Rake::Task['dotnet:package'].invoke('--config=release') api_key = ENV.fetch('NUGET_API_KEY', nil) push_destination = 'https://api.nuget.org/v3/index.json' if nightly # Nightly builds are pushed to GitHub NuGet repository # This commands will run in GitHub Actions api_key = ENV.fetch('GITHUB_TOKEN', nil) github_push_url = 'https://nuget.pkg.github.com/seleniumhq/index.json' push_destination = 'github' flags = ['--username', 'seleniumhq', '--password', api_key, '--store-password-in-clear-text', '--name', push_destination, github_push_url] sh "dotnet nuget add source #{flags.join(' ')}" end ["./bazel-bin/dotnet/src/webdriver/Selenium.WebDriver.#{dotnet_version}.nupkg", "./bazel-bin/dotnet/src/support/Selenium.Support.#{dotnet_version}.nupkg"].each do |asset| sh "dotnet nuget push #{asset} --api-key #{api_key} --source #{push_destination}" end end desc 'Generate .NET documentation' task :docs, [:skip_update] do |_task, arguments| FileUtils.rm_rf('build/docs/api/dotnet/') begin # Pinning to 2.78.2 to avoid breaking changes in newer versions sh 'dotnet tool uninstall --global docfx || true' sh 'dotnet tool install --global --version 2.78.2 docfx' # sh 'dotnet tool update -g docfx' rescue StandardError puts 'Please ensure that .NET SDK is installed.' raise end begin sh 'docfx dotnet/docs/docfx.json' rescue StandardError case $CHILD_STATUS.exitstatus when 133 raise 'Ensure the dotnet/tools directory is added to your PATH environment variable (e.g., `~/.dotnet/tools`)' when 255 puts '.NET documentation build failed, likely because of DevTools namespacing. This is ok; continuing' else raise end end update_gh_pages unless arguments[:skip_update] end desc 'Update .NET changelog' task :changelog do header = "v#{dotnet_version}\n======" update_changelog(dotnet_version, 'dotnet', 'dotnet/src/', 'dotnet/CHANGELOG', header) end desc 'Update .NET version' task :version, [:version] do |_task, arguments| old_version = dotnet_version nightly = "-nightly#{Time.now.strftime('%Y%m%d%H%M')}" new_version = updated_version(old_version, arguments[:version], nightly) file = 'dotnet/selenium-dotnet-version.bzl' text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } Rake::Task['dotnet:changelog'].invoke unless new_version.include?(nightly) end end namespace :java do desc 'Build Java Client Jars' task :build do |_task, arguments| args = arguments.to_a.compact JAVA_RELEASE_TARGETS.each { |target| Bazel.execute('build', args, target) } end desc 'Build Grid Server' task :grid do |_task, arguments| args = arguments.to_a.compact Bazel.execute('build', args, '//java/src/org/openqa/selenium/grid:executable-grid') end desc 'Package Java bindings and grid into releasable packages and stage for release' task :package do |_task, arguments| args = arguments.to_a.compact.empty? ? ['--config=release'] : arguments.to_a.compact Bazel.execute('build', args, '//java/src/org/openqa/selenium:client-zip') Bazel.execute('build', args, '//java/src/org/openqa/selenium/grid:server-zip') Bazel.execute('build', args, '//java/src/org/openqa/selenium/grid:executable-grid') mkdir_p 'build/dist' Dir.glob('build/dist/*{java,server}*').each { |file| FileUtils.rm_f(file) } FileUtils.copy('bazel-bin/java/src/org/openqa/selenium/grid/server-zip.zip', "build/dist/selenium-server-#{java_version}.zip") FileUtils.chmod(0o666, "build/dist/selenium-server-#{java_version}.zip") FileUtils.copy('bazel-bin/java/src/org/openqa/selenium/client-zip.zip', "build/dist/selenium-java-#{java_version}.zip") FileUtils.chmod(0o666, "build/dist/selenium-java-#{java_version}.zip") FileUtils.copy('bazel-bin/java/src/org/openqa/selenium/grid/selenium', "build/dist/selenium-server-#{java_version}.jar") FileUtils.chmod(0o777, "build/dist/selenium-server-#{java_version}.jar") end desc 'Deploy all jars to Maven' task :release do |_task, arguments| args = arguments.to_a.compact nightly = args.delete('nightly') ENV['MAVEN_USER'] ||= ENV.fetch('SEL_M2_USER', nil) ENV['MAVEN_PASSWORD'] ||= ENV.fetch('SEL_M2_PASS', nil) read_m2_user_pass unless ENV['MAVEN_PASSWORD'] && ENV['MAVEN_USER'] repo = nightly ? 'content/repositories/snapshots' : 'service/local/staging/deploy/maven2' ENV['MAVEN_REPO'] = "https://oss.sonatype.org/#{repo}" ENV['GPG_SIGN'] = (!nightly).to_s Rake::Task['java:version'].invoke if nightly Rake::Task['java:package'].invoke('--config=release') Rake::Task['java:build'].invoke('--config=release') # Because we want to `run` things, we can't use the `release` config JAVA_RELEASE_TARGETS.each { |target| Bazel.execute('run', ['--config=release'], target) } end desc 'Install jars to local m2 directory' task install: :'maven-install' desc 'Generate Java documentation' task :docs, [:skip_update] do |_task, arguments| Rake::Task['javadocs'].invoke update_gh_pages unless arguments[:skip_update] end desc 'Update Maven dependencies' task :update do # Make sure things are in a good state to start with args = ['--action_env=RULES_JVM_EXTERNAL_REPIN=1'] Bazel.execute('run', args, '@maven//:pin') file_path = 'MODULE.bazel' content = File.read(file_path) # For some reason ./go wrapper is not outputting from Open3, so cannot use Bazel class directly output = `bazel run @maven//:outdated` output.scan(/\S+ \[\S+-alpha\]/).each do |match| puts "WARNING — Cannot automatically update alpha version of: #{match}" end versions = output.scan(/(\S+) \[\S+ -> (\S+)\]/).to_h versions.each do |artifact, version| if artifact.match?('graphql') puts 'WARNING — Cannot automatically update graphql' next end replacement = artifact.include?('googlejavaformat') ? "#{artifact}:jar:#{version}" : "#{artifact}:#{version}" content.gsub!(/#{artifact}:(jar:)?\d+\.\d+[^\\"]+/, replacement) end File.write(file_path, content) args = ['--action_env=RULES_JVM_EXTERNAL_REPIN=1'] Bazel.execute('run', args, '@maven//:pin') end desc 'Update Java changelog' task :changelog do header = "v#{java_version}\n======" update_changelog(java_version, 'java', 'java/src/org/', 'java/CHANGELOG', header) end desc 'Update Java version' task :version, [:version] do |_task, arguments| old_version = java_version new_version = updated_version(old_version, arguments[:version], '-SNAPSHOT') file = 'java/version.bzl' text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } Rake::Task['java:changelog'].invoke unless new_version.include?('-SNAPSHOT') end end def rust_version File.foreach('rust/BUILD.bazel') do |line| return line.split('=').last.strip.tr('",', '') if line.include?('version =') end end namespace :rust do desc 'Build Selenium Manager' task :build do |_task, arguments| args = arguments.to_a.compact Bazel.execute('build', args, '//rust:selenium-manager') end desc 'Update the rust lock files' task :update do sh 'CARGO_BAZEL_REPIN=true bazel sync --only=crates' end desc 'Update Rust changelog' task :changelog do header = "#{rust_version}\n======" version = rust_version.split('.').tap(&:shift).join('.') update_changelog(version, 'rust', 'rust/src', 'rust/CHANGELOG.md', header) end # Rust versioning is currently difficult compared to the others because we are using the 0.4.x pattern # until Selenium Manager comes out of beta desc 'Update Rust version' task :version, [:version] do |_task, arguments| old_version = rust_version.dup equivalent_version = if old_version.include?('nightly') "#{old_version.split(/\.|-/)[0...-1].tap(&:shift).join('.')}.0-nightly" else old_version.split('.').tap(&:shift).append('0').join('.') end updated = updated_version(equivalent_version, arguments[:version], '-nightly') new_version = updated.split(/\.|-/).tap { |v| v.delete_at(2) }.unshift('0').join('.').gsub('.nightly', '-nightly') ['rust/Cargo.toml', 'rust/BUILD.bazel'].each do |file| text = File.read(file).gsub(old_version, new_version) File.open(file, 'w') { |f| f.puts text } end Rake::Task['rust:changelog'].invoke unless new_version.include?('-nightly') Rake::Task['rust:update'].invoke end # Creating a special task for this because Rust version needs to be managed at a different place than # everything else; want to use changelog updates later in process namespace :version do desc 'Commits updates from Rust version changes' task :commit do @git.reset commit!("update Rust version to #{rust_version}", ['rust/BUILD.bazel', 'rust/Cargo.Bazel.lock', 'rust/Cargo.lock', 'rust/Cargo.toml']) commit!('Rust Changelog', ['rust/CHANGELOG.md']) end end end namespace :all do desc 'Update all API Documentation' task :docs do Rake::Task['java:docs'].invoke(true) Rake::Task['py:docs'].invoke(true) Rake::Task['rb:docs'].invoke(true) Rake::Task['dotnet:docs'].invoke(true) Rake::Task['node:docs'].invoke(true) update_gh_pages end desc 'Build all artifacts for all language bindings' task :build do |_task, arguments| args = arguments.to_a.compact Rake::Task['java:build'].invoke(*args) Rake::Task['py:build'].invoke(*args) Rake::Task['rb:build'].invoke(*args) Rake::Task['dotnet:build'].invoke(*args) Rake::Task['node:build'].invoke(*args) end desc 'Package or build stamped artifacts for distribution in GitHub Release assets' task :package do |_task, arguments| args = arguments.to_a.compact Rake::Task['java:package'].invoke(*args) Rake::Task['dotnet:package'].invoke(*args) end desc 'Release all artifacts for all language bindings' task :release do |_task, arguments| Rake::Task['clean'].invoke args = arguments.to_a.compact.empty? ? ['--stamp'] : arguments.to_a.compact Rake::Task['java:release'].invoke(*args) Rake::Task['py:release'].invoke(*args) Rake::Task['rb:release'].invoke(*args) Rake::Task['dotnet:release'].invoke(*args) Rake::Task['node:release'].invoke(*args) # TODO: Update this so it happens in each language, but does not commit Rake::Task['all:version'].invoke('nightly') puts 'Committing nightly version updates' commit!('update versions to nightly', ['dotnet/selenium-dotnet-version.bzl', 'java/version.bzl', 'javascript/node/selenium-webdriver/BUILD.bazel', 'javascript/node/selenium-webdriver/package.json', 'py/docs/source/conf.py', 'py/selenium/webdriver/__init__.py', 'py/selenium/__init__.py', 'py/BUILD.bazel', 'rb/lib/selenium/webdriver/version.rb', 'rb/Gemfile.lock']) print 'Do you want to push the committed changes? (Y/n): ' response = $stdin.gets.chomp.downcase @git.push if %w[y yes].include?(response) end task :lint do ext = /mswin|msys|mingw|cygwin|bccwin|wince|emc/.match?(RbConfig::CONFIG['host_os']) ? 'ps1' : 'sh' sh "./scripts/format.#{ext}", verbose: true Rake::Task['py:lint'].invoke end desc 'Update everything in preparation for a release' task :prepare, [:version, :channel] do |_task, arguments| chrome_channel = arguments[:channel] || 'Stable' version = arguments[:version] args = Array(chrome_channel) ? ['--', "--chrome_channel=#{chrome_channel.capitalize}"] : [] Bazel.execute('run', args, '//scripts:pinned_browsers') commit!('Update pinned browser versions', ['common/repositories.bzl']) Bazel.execute('run', args, '//scripts:update_cdp') commit!('Update supported versions for Chrome DevTools', ['common/devtools/', 'dotnet/src/webdriver/DevTools/', 'dotnet/src/webdriver/WebDriver.csproj', 'dotnet/test/common/DevTools/', 'dotnet/test/common/CustomDriverConfigs/', 'dotnet/selenium-dotnet-version.bzl', 'java/src/org/openqa/selenium/devtools/', 'javascript/node/selenium-webdriver/BUILD.bazel', 'py/BUILD.bazel', 'rb/lib/selenium/devtools/', 'rb/Gemfile.lock', 'Rakefile']) Bazel.execute('run', args, '//scripts:selenium_manager') commit!('Update selenium manager version', ['common/selenium_manager.bzl']) Rake::Task['java:update'].invoke commit!('Update Maven Dependencies', ['java/maven_deps.bzl', 'java/maven_install.json']) Rake::Task['authors'].invoke commit!('Update authors file', ['AUTHORS']) # Note that this does not include Rust version changes that are handled in separate rake:version task # TODO: These files are all defined in other tasks; remove duplication Rake::Task['all:version'].invoke(version) commit!("FIX CHANGELOGS BEFORE MERGING!\n\nUpdate versions and change logs to release Selenium #{java_version}", ['dotnet/CHANGELOG', 'dotnet/selenium-dotnet-version.bzl', 'java/CHANGELOG', 'java/version.bzl', 'javascript/node/selenium-webdriver/CHANGES.md', 'javascript/node/selenium-webdriver/package.json', 'py/docs/source/conf.py', 'py/selenium/__init__.py', 'py/selenium/webdriver/__init__.py', 'py/BUILD.bazel', 'py/CHANGES', 'rb/lib/selenium/webdriver/version.rb', 'rb/CHANGES', 'rb/Gemfile.lock', 'rust/CHANGELOG.md', 'rust/BUILD.bazel', 'rust/Cargo.Bazel.lock', 'rust/Cargo.toml', 'rust/Cargo.lock']) end desc 'Update all versions' task :version, [:version] do |_task, arguments| version = arguments[:version] || 'nightly' Rake::Task['java:version'].invoke(version) Rake::Task['rb:version'].invoke(version) Rake::Task['node:version'].invoke(version) Rake::Task['py:version'].invoke(version) Rake::Task['dotnet:version'].invoke(version) Rake::Task['rust:version'].invoke(version) end end at_exit do system 'sh', '.git-fixfiles' if File.exist?('.git') && SeleniumRake::Checks.linux? end def updated_version(current, desired = nil, nightly = nil) if !desired.nil? && desired != 'nightly' # If desired is present, return full 3 digit version desired.split('.').tap { |v| v << 0 while v.size < 3 }.join('.') elsif current.split(/\.|-/).size > 3 # if current version is already nightly, just need to bump it; this will be noop for some languages pattern = /-?\.?(nightly|SNAPSHOT|dev|\d{12})\d*$/ current.gsub(pattern, nightly) elsif current.split(/\.|-/).size == 3 # if current version is not nightly, need to bump the version and make nightly "#{current.split(/\.|-/).tap { |i| (i[1] = i[1].to_i + 1) && (i[2] = 0) }.join('.')}#{nightly}" end end def update_gh_pages @git.fetch('origin', {ref: 'gh-pages'}) @git.checkout('gh-pages', force: true) %w[java rb py dotnet javascript].each do |language| next unless Dir.exist?("build/docs/api/#{language}") && !Dir.empty?("build/docs/api/#{language}") FileUtils.rm_rf("docs/api/#{language}") FileUtils.mv("build/docs/api/#{language}", "docs/api/#{language}") commit!("updating #{language} API docs", ["docs/api/#{language}/"]) end end def restore_git(origin_reference) puts 'Stashing docs changes for gh-pages' Git::Stash.new(@git, 'docs changes for gh-pages') puts "Checking out originating branch/tag — #{origin_reference}" @git.checkout(origin_reference) false end def previous_tag(current_version, language = nil) version = current_version.split(/\.|-/) if version.size > 3 puts 'WARNING - Changelogs not updated when set to prerelease' elsif version[2].to_i > 1 # specified as patch release patch_version = (version[2].to_i - 1).to_s "selenium-#{[[version[0]], version[1], patch_version].join('.')}-#{language}" elsif version[2] == '1' # specified as patch release; special case "selenium-#{[[version[0]], version[1], '0'].join('.')}" else minor_version = (version[1].to_i - 1) tags = @git.tags.map(&:name) tag = language ? tags.reverse.find { |t| t.match?(/selenium-4\.#{minor_version}.*-#{language}/) } : nil tag || "selenium-#{[[version[0]], minor_version, '0'].join('.')}" end end def update_changelog(version, language, path, changelog, header) tag = previous_tag(version, language) log = if language == 'javascript' `git --no-pager log #{tag}...HEAD --pretty=format:"- %s" --reverse #{path}` else `git --no-pager log #{tag}...HEAD --pretty=format:"* %s" --reverse #{path}` end commits = log.split('>>>').map { |entry| lines = entry.split("\n") lines.reject! { |line| line.match?(/^(----|Co-authored|Signed-off)/) || line.empty? } lines.join("\n") }.join("\n>>>") File.open(changelog, 'r+') do |file| new_content = "#{header}\n#{commits}\n\n#{file.read}" file.rewind file.write(new_content) file.truncate(file.pos) end end def commit!(message, files = [], all: false) files.each do |file| puts "adding: #{file}" @git.add(file) end all ? @git.commit_all(message) : @git.commit(message) rescue Git::FailedError => e puts e.message end