From 3d919b2634d0e96f531f9624e8d275fd35d2316d Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Tue, 16 Jun 2026 12:04:44 +0100 Subject: [PATCH] [rust] Cross-compile selenium-manager for all platforms Cross-compiles selenium-manager for every supported OS/arch via rules_rs platform transitions + the hermetic @llvm toolchain (musl on Linux, gnullvm on Windows), so release bundles can build all platform binaries from a single host. Bindings (Java, Python, Ruby, JS, .NET) updated to probe per-(os, arch) paths from the bundle. Rebased onto trunk's rules_rs 0.0.90 / @llvm-on-macOS work (#17683). --- .bazelrc | 9 + MODULE.bazel | 96 ++++++- common/BUILD.bazel | 19 ++ common/manager/BUILD.bazel | 74 +++-- common/manager/defs.bzl | 16 ++ dotnet/private/nuget_pack.bzl | 18 +- dotnet/src/webdriver/BUILD.bazel | 54 ++-- .../src/webdriver/Manager/SeleniumManager.cs | 40 +-- .../src/webdriver/Selenium.WebDriver.csproj | 10 +- .../src/webdriver/Selenium.WebDriver.nuspec | 9 +- .../nuget/build/Selenium.WebDriver.props | 33 ++- dotnet/test/webdriver/BUILD.bazel | 4 +- .../org/openqa/selenium/manager/BUILD.bazel | 23 +- .../selenium/manager/SeleniumManager.java | 33 ++- javascript/selenium-webdriver/BUILD.bazel | 52 ++-- .../common/seleniumManager.js | 21 +- py/BUILD.bazel | 56 ++-- .../webdriver/common/selenium_manager.py | 35 +-- .../common/selenium_manager_tests.py | 36 ++- rb/BUILD.bazel | 52 ++-- .../webdriver/common/selenium_manager.rb | 29 +- .../selenium/webdriver/BUILD.bazel | 4 +- .../webdriver/common/selenium_manager_spec.rb | 29 +- rust/BUILD.bazel | 252 ++++++++++++++---- rust/tests/BUILD.bazel | 4 +- third_party/bazel/BUILD.bazel | 1 + .../bazel/llvm_libgcc_s_universal_scope.patch | 10 + .../bazel/windows_link_readme_case.patch | 6 + .../bazel/windows_targets_readme_case.patch | 6 + 29 files changed, 759 insertions(+), 272 deletions(-) create mode 100644 common/manager/defs.bzl create mode 100644 third_party/bazel/llvm_libgcc_s_universal_scope.patch create mode 100644 third_party/bazel/windows_link_readme_case.patch create mode 100644 third_party/bazel/windows_targets_readme_case.patch diff --git a/.bazelrc b/.bazelrc index 72f27e448645b..ff9f4c9a1768a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,6 +10,12 @@ try-import %workspace%/.bazelrc.windows.local common --lockfile_mode=off +# Skip Bazel's auto-detection of the host C/C++ toolchain. We register the +# hermetic LLVM toolchain explicitly in MODULE.bazel for all platforms and +# don't want rules_cc's auto-detected `local_config_cc` to compete with it. +common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 +common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 + # Bazel 8 compatibility flags. We should find ways to avoid needing these common --legacy_external_runfiles @@ -89,6 +95,9 @@ build:windows --host_per_file_copt=external/protobuf\\+.*@/w # For build stamping common --enable_platform_specific_config common:linux --host_platform=//:local_linux_gnu +# The LLVM toolchain doesn't ship libgcc_s; this stub satisfies Rust's +# glibc stdlib link dependency when LLVM is the host C++ toolchain. +common:linux --@llvm//config:experimental_stub_libgcc_s=True common:windows --host_platform=//:local_windows_gnullvm # rules_rs 0.0.90 bundles a newer rules_rust whose cargo build-script runner diff --git a/MODULE.bazel b/MODULE.bazel index c3e641b34ce6f..863f4a12138ab 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -26,6 +26,24 @@ single_version_override( version = "33.5", ) +# We set `--@llvm//config:experimental_stub_libgcc_s=True` in .bazelrc so the +# LLVM cc toolchain doesn't try to resolve a `-lgcc_s` it doesn't ship. In +# v0.8.0 the `experimental_stub_libgcc_s` bool_flag isn't marked +# `scope = "universal"`, so the flag value on the target config doesn't +# propagate to exec config and tool builds (process_wrapper, +# //rust:_selenium-manager-bin) still fail to link. The patch adds +# `scope = "universal"` so the flag does propagate; can be dropped once +# hermeticbuild/hermetic-llvm#544 lands. +single_version_override( + module_name = "llvm", + patch_strip = 1, + patches = [ + "//third_party/bazel:llvm_libgcc_s_universal_scope.patch", + ], + version = "0.8.3", +) + +bazel_dep(name = "rules_android", version = "0.7.2") bazel_dep(name = "rules_closure", version = "0.16.0") bazel_dep(name = "rules_dotnet", version = "0.21.5") @@ -41,6 +59,16 @@ bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "rules_ruby", version = "0.26.0") bazel_dep(name = "rules_rs", version = "0.0.90") +# Native C libraries pulled in by the `*-sys` crates that the Selenium +# Manager Rust binary depends on. Pointing each `*-sys` crate at its +# BCR-hosted cc_library (per rules_rs's `bazel mod tidy` suggestion) lets +# the C source build with the LLVM cc toolchain — which is target-libc +# aware, so the same crate compiles correctly for both the host and the +# musl cross-compile targets. +bazel_dep(name = "bzip2", version = "1.0.8.bcr.3") +bazel_dep(name = "xz", version = "5.4.5.bcr.8") +bazel_dep(name = "zstd", version = "1.5.7.bcr.1") + single_version_override( module_name = "rules_jvm_external", patch_strip = 1, @@ -389,15 +417,30 @@ use_repo(rust_toolchains, "default_rust_toolchains") register_toolchains("@default_rust_toolchains//:all") -# TODO: Register only Windows exec for now, full @llvm migration can be done as followup. llvm_toolchains = use_extension("@llvm//extensions:toolchain.bzl", "toolchain") + +# Register the hermetic LLVM exec toolchain for every platform we build on so +# that the C/C++ toolchain used for exec-config targets (process_wrapper, cargo +# build scripts, etc.) is fully version-pinned and reproducible. +# +# macOS is now viable because the Rust TLS stack uses `ring` (pure Rust + light +# C, no system-framework deps) instead of `aws-lc-rs` / `aws-lc-sys`, which +# previously required CoreServices and compiler-rt from the Xcode sysroot. +# +# Windows uses the gnullvm ABI (PE binary, MSVCRT runtime) rather than MSVC +# because the hermetic LLVM toolchain ships everything needed for gnullvm +# targets without a separately-provisioned Visual Studio installation. llvm_toolchains.exec( arch = "x86_64", - os = "windows", + os = "linux", ) llvm_toolchains.exec( arch = "aarch64", - os = "windows", + os = "linux", +) +llvm_toolchains.exec( + arch = "x86_64", + os = "macos", ) llvm_toolchains.exec( arch = "aarch64", @@ -405,7 +448,11 @@ llvm_toolchains.exec( ) llvm_toolchains.exec( arch = "x86_64", - os = "macos", + os = "windows", +) +llvm_toolchains.exec( + arch = "aarch64", + os = "windows", ) use_repo(llvm_toolchains, "llvm_toolchains") @@ -418,25 +465,60 @@ crate.from_cargo( cargo_toml = "//rust:Cargo.toml", platform_triples = [ "aarch64-apple-darwin", + "aarch64-pc-windows-gnullvm", "aarch64-pc-windows-msvc", "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", "x86_64-apple-darwin", + "x86_64-pc-windows-gnullvm", "x86_64-pc-windows-msvc", "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", ], ) + +# Both crates ship `readme.md` but their source uses `include_str!("../readme.md")`. +# Bazel's Rust sandbox only includes `.rs` source files — not auxiliary data files +# like README — so the include_str! fails at compile time regardless of case. We +# patch out the doc attribute entirely; no documentation needed in a Bazel build. +crate.annotation( + crate = "windows-link", + patch_args = ["-p1"], + patches = ["//third_party/bazel:windows_link_readme_case.patch"], +) +crate.annotation( + crate = "windows-targets", + patch_args = ["-p1"], + patches = ["//third_party/bazel:windows_targets_readme_case.patch"], + version = "0.53.4", +) + +# Point the `*-sys` crates at the BCR cc_library packages so their cargo +# build scripts don't run. The `inject_repo` calls below make the +# corresponding repos visible to the crate extension. (Auto-suggested by +# `bazel mod tidy` against rules_rs's well-known annotations.) crate.annotation( crate = "bzip2-sys", - gen_build_script = "on", + gen_build_script = "off", + deps = ["@bzip2//:bz2"], ) crate.annotation( crate = "lzma-sys", - gen_build_script = "on", + gen_build_script = "off", + deps = ["@xz//:lzma"], ) crate.annotation( crate = "zstd-sys", - gen_build_script = "on", + gen_build_script = "off", + deps = ["@zstd"], ) + +inject_repo(crate, "bzip2") + +inject_repo(crate, "xz") + +inject_repo(crate, "zstd") + use_repo(crate, "crates") selenium_manager_artifacts = use_extension("//common:selenium_manager.bzl", "selenium_manager_artifacts") diff --git a/common/BUILD.bazel b/common/BUILD.bazel index f267704b2dd06..1ca526c968560 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -41,6 +41,25 @@ config_setting( constraint_values = ["@platforms//os:windows"], ) +# Per-(OS, arch) host config_settings. +[ + config_setting( + name = "{}_{}".format(os, arch), + constraint_values = [ + "@platforms//os:{}".format(os), + "@platforms//cpu:{}".format(arch), + ], + ) + for (os, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("macos", "x86_64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] + # Are we creating a stamped build? config_setting( name = "stamp", diff --git a/common/manager/BUILD.bazel b/common/manager/BUILD.bazel index ababff738ce51..21d1dfa0b4348 100644 --- a/common/manager/BUILD.bazel +++ b/common/manager/BUILD.bazel @@ -1,3 +1,5 @@ +load("//common:defs.bzl", "copy_file") + package( default_visibility = [ "//dotnet/src/webdriver:__pkg__", @@ -9,25 +11,63 @@ package( ], ) +# Tree of every cross-built Selenium Manager binary, laid out as +# `-/selenium-manager[.exe]`. Language packagers that can ship +# directories (Ruby gem data, npm package files, Python wheel data) take +# this directly; tools that only handle individual files (Java resources, +# nuget pack) consume the per-arch single-file targets below. alias( - name = "selenium-manager-linux", - actual = select({ - "//common:use_pinned_browser": "@download_sm_linux//file", - "//conditions:default": "//rust:selenium-manager-linux", - }), + name = "selenium-manager-bundle", + actual = "//rust:selenium-manager-bundle", ) -alias( - name = "selenium-manager-macos", - actual = select({ - "//common:use_pinned_browser": "@download_sm_macos//file", - "//conditions:default": "//rust:selenium-manager-macos", - }), -) +# Single-file targets per (os, arch) — same binaries the bundle contains, +# but as individual files (each output at `-/selenium-manager[.exe]`) +# for tools whose resource attrs don't expand tree artifacts (Java +# `resources`, nuget_pack `files`). +# +# On Windows hosts the cross-compile chain through rules_rs's LLVM +# toolchain currently fails for proc-macro deps, so we fall back to the +# native rust_binary for the host's architecture (via `//rust:_selenium-manager-bin`) +# and stub the rest. Release jars/nupkgs are built on Linux, where every +# entry is real. +[ + copy_file( + name = "selenium-manager-{}-{}".format(os, arch), + src = select({ + "//common:windows_aarch64": ( + "//rust:_selenium-manager-bin" if (os, arch) == ("windows", "aarch64") else "//rust:_selenium-manager-stub" + ), + "//common:windows_x86_64": ( + "//rust:_selenium-manager-bin" if (os, arch) == ("windows", "x86_64") else "//rust:_selenium-manager-stub" + ), + "//conditions:default": "//rust:_bundle_member_{}_{}".format(os, arch), + }), + out = "{}-{}/selenium-manager{}".format( + os, + arch, + ".exe" if os == "windows" else "", + ), + is_executable = True, + ) + for (os, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("macos", "x86_64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -# The Rust selenium-manager cannot be built from source on Windows (pending the -# @llvm toolchain migration) -alias( - name = "selenium-manager-windows", - actual = "@download_sm_windows//file", +filegroup( + name = "selenium-manager-files", + srcs = [ + ":selenium-manager-linux-aarch64", + ":selenium-manager-linux-x86_64", + ":selenium-manager-macos-aarch64", + ":selenium-manager-macos-x86_64", + ":selenium-manager-windows-aarch64", + ":selenium-manager-windows-x86_64", + ], ) diff --git a/common/manager/defs.bzl b/common/manager/defs.bzl new file mode 100644 index 0000000000000..367c93e5357d6 --- /dev/null +++ b/common/manager/defs.bzl @@ -0,0 +1,16 @@ +load("@aspect_bazel_lib//lib:copy_directory.bzl", "copy_directory") + +def selenium_manager_bundle(name, out, **kwargs): + """Copies the cross-compiled Selenium Manager bundle tree into this package. + + Args: + name: rule name + out: destination path within the package's output tree + **kwargs: forwarded to copy_directory (e.g. visibility) + """ + copy_directory( + name = name, + src = "//common/manager:selenium-manager-bundle", + out = out, + **kwargs + ) diff --git a/dotnet/private/nuget_pack.bzl b/dotnet/private/nuget_pack.bzl index 76b2ed92f8fac..58d22189f660a 100644 --- a/dotnet/private/nuget_pack.bzl +++ b/dotnet/private/nuget_pack.bzl @@ -77,14 +77,22 @@ def nuget_pack_impl(ctx): working_dir = ctx.label.name + "-working-dir" - # Copy files directly into the working directory layout (no intermediate zip) + # Copy files directly into the working directory layout (no intermediate zip). + # Tree artifacts (directories produced e.g. by copy_to_directory) are copied + # recursively into the destination directory. copy_cmds = [] for (file, rel_path) in layout.items(): dest = working_dir + "/" + rel_path - copy_cmds.append("mkdir -p \"$(dirname '{dest}')\" && cp '{src}' '{dest}'".format( - dest = dest, - src = file.path, - )) + if file.is_directory: + copy_cmds.append("mkdir -p '{dest}' && cp -R '{src}'/. '{dest}'".format( + dest = dest, + src = file.path, + )) + else: + copy_cmds.append("mkdir -p \"$(dirname '{dest}')\" && cp '{src}' '{dest}'".format( + dest = dest, + src = file.path, + )) cmd_parts = [ "rm -rf '%s'" % working_dir, diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel index 79cfb38199640..273e860488464 100644 --- a/dotnet/src/webdriver/BUILD.bazel +++ b/dotnet/src/webdriver/BUILD.bazel @@ -149,24 +149,40 @@ copy_file( out = "transitiveSelenium.WebDriver.props", ) -copy_file( - name = "manager-linux", - src = "//common/manager:selenium-manager-linux", - out = "manager/linux/selenium-manager", - visibility = ["//dotnet/test/webdriver:__pkg__"], -) - -copy_file( - name = "manager-macos", - src = "//common/manager:selenium-manager-macos", - out = "manager/macos/selenium-manager", - visibility = ["//dotnet/test/webdriver:__pkg__"], -) +[ + copy_file( + name = "manager-{}-{}".format(os_name, arch), + src = "//common/manager:selenium-manager-{}-{}".format(os_name, arch), + out = "manager/{}-{}/selenium-manager{}".format( + os_name, + arch, + ".exe" if os_name == "windows" else "", + ), + is_executable = True, + ) + for (os_name, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -copy_file( - name = "manager-windows", - src = "//common/manager:selenium-manager-windows", - out = "manager/windows/selenium-manager.exe", +filegroup( + name = "manager-bundle", + srcs = select({ + "//common:windows_x86_64": [":manager-windows-x86_64"], + "//common:windows_aarch64": [":manager-windows-aarch64"], + "//common:macos_aarch64": [":manager-macos-aarch64"], + "//conditions:default": [ + ":manager-linux-aarch64", + ":manager-linux-x86_64", + ":manager-macos-aarch64", + ":manager-windows-aarch64", + ":manager-windows-x86_64", + ], + }), visibility = ["//dotnet/test/webdriver:__pkg__"], ) @@ -174,9 +190,7 @@ nuget_pack( name = "webdriver-pack", files = { "//common/images:selenium_logo_small.png": "icon.png", - "//common/manager:selenium-manager-linux": "manager/linux/selenium-manager", - "//common/manager:selenium-manager-macos": "manager/macos/selenium-manager", - "//common/manager:selenium-manager-windows": "manager/windows/selenium-manager.exe", + "//common/manager:selenium-manager-bundle": "manager", ":assets-nuget-readme": "README.md", ":assets-nuget-build-props": "build/Selenium.WebDriver.props", ":assets-nuget-buildtransitive-props": "buildTransitive/Selenium.WebDriver.props", diff --git a/dotnet/src/webdriver/Manager/SeleniumManager.cs b/dotnet/src/webdriver/Manager/SeleniumManager.cs index d11f657e07495..dde9d74a3bdda 100644 --- a/dotnet/src/webdriver/Manager/SeleniumManager.cs +++ b/dotnet/src/webdriver/Manager/SeleniumManager.cs @@ -19,9 +19,7 @@ using System.Diagnostics; using System.Globalization; -#if !NET8_0_OR_GREATER using System.Runtime.InteropServices; -#endif using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -120,6 +118,30 @@ public static partial class SeleniumManager $"Selenium Manager doesn't support your runtime platform: {Environment.OSVersion.Platform}"), }; +#if !NET462 + var processArchitecture = RuntimeInformation.ProcessArchitecture; + var archSuffix = processArchitecture switch + { + Architecture.X64 => "x64", + Architecture.Arm64 => "arm64", + _ => throw new PlatformNotSupportedException( + $"Selenium Manager doesn't support your runtime architecture: {processArchitecture}"), + }; + +#else + const string archSuffix = "x64"; +#endif + + var ridOs = platform switch + { + SupportedPlatform.Windows => "win", + SupportedPlatform.Linux => "linux", + SupportedPlatform.MacOS => "osx", + _ => throw new PlatformNotSupportedException( + $"Selenium Manager doesn't support your runtime platform: {Environment.OSVersion.Platform}"), + }; + var rid = $"{ridOs}-{archSuffix}"; + var baseDirectory = AppContext.BaseDirectory; List probingPaths = []; @@ -127,19 +149,7 @@ public static partial class SeleniumManager if (baseDirectory is not null) { probingPaths.Add(Path.Combine(baseDirectory, seleniumManagerFileName)); - - switch (platform) - { - case SupportedPlatform.Windows: - probingPaths.Add(Path.Combine(baseDirectory, "runtimes", "win", "native", seleniumManagerFileName)); - break; - case SupportedPlatform.Linux: - probingPaths.Add(Path.Combine(baseDirectory, "runtimes", "linux", "native", seleniumManagerFileName)); - break; - case SupportedPlatform.MacOS: - probingPaths.Add(Path.Combine(baseDirectory, "runtimes", "osx", "native", seleniumManagerFileName)); - break; - } + probingPaths.Add(Path.Combine(baseDirectory, "runtimes", rid, "native", seleniumManagerFileName)); } #if !NET462 diff --git a/dotnet/src/webdriver/Selenium.WebDriver.csproj b/dotnet/src/webdriver/Selenium.WebDriver.csproj index ea2f8a07a7997..4ec08c49bce32 100644 --- a/dotnet/src/webdriver/Selenium.WebDriver.csproj +++ b/dotnet/src/webdriver/Selenium.WebDriver.csproj @@ -61,7 +61,7 @@ - + @@ -70,9 +70,11 @@ - - - + + + + + diff --git a/dotnet/src/webdriver/Selenium.WebDriver.nuspec b/dotnet/src/webdriver/Selenium.WebDriver.nuspec index ee82fe2d42370..14be431aa20a1 100644 --- a/dotnet/src/webdriver/Selenium.WebDriver.nuspec +++ b/dotnet/src/webdriver/Selenium.WebDriver.nuspec @@ -61,9 +61,12 @@ - - - + + + + + + diff --git a/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props b/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props index 31e59250fd6d3..bceda22a3359f 100644 --- a/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props +++ b/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props @@ -4,22 +4,43 @@ - - runtimes\win\native\%(Filename)%(Extension) + + runtimes\win-x64\native\%(Filename)%(Extension) PreserveNewest False false - - runtimes\linux\native\%(Filename)%(Extension) + + runtimes\win-arm64\native\%(Filename)%(Extension) PreserveNewest False false - - runtimes\osx\native\%(Filename)%(Extension) + + runtimes\linux-x64\native\%(Filename)%(Extension) + PreserveNewest + False + false + + + + runtimes\linux-arm64\native\%(Filename)%(Extension) + PreserveNewest + False + false + + + + runtimes\osx-arm64\native\%(Filename)%(Extension) + PreserveNewest + False + false + + + + runtimes\osx-x64\native\%(Filename)%(Extension) PreserveNewest False false diff --git a/dotnet/test/webdriver/BUILD.bazel b/dotnet/test/webdriver/BUILD.bazel index b1161a0f296e8..efe20b49ddd9b 100644 --- a/dotnet/test/webdriver/BUILD.bazel +++ b/dotnet/test/webdriver/BUILD.bazel @@ -8,9 +8,7 @@ filegroup( "appconfig.json", "//common/extensions", "//common/src/web", - "//dotnet/src/webdriver:manager-linux", - "//dotnet/src/webdriver:manager-macos", - "//dotnet/src/webdriver:manager-windows", + "//dotnet/src/webdriver:manager-bundle", "//javascript/atoms", "//third_party/closure/goog", "//third_party/js/selenium:webdriver_json", diff --git a/java/src/org/openqa/selenium/manager/BUILD.bazel b/java/src/org/openqa/selenium/manager/BUILD.bazel index f0c3d396516e2..1d7212b199ff9 100644 --- a/java/src/org/openqa/selenium/manager/BUILD.bazel +++ b/java/src/org/openqa/selenium/manager/BUILD.bazel @@ -1,4 +1,3 @@ -load("//common:defs.bzl", "copy_file") load("//java:defs.bzl", "java_export") load("//java:version.bzl", "SE_VERSION") @@ -11,9 +10,7 @@ java_export( ], pom_template = "//java/src/org/openqa/selenium:template-pom", resources = [ - ":manager-linux", - ":manager-macos", - ":manager-windows", + "//common/manager:selenium-manager-files", ], tags = [ "release-artifact", @@ -28,21 +25,3 @@ java_export( "@maven//:org_jspecify_jspecify", ], ) - -copy_file( - name = "manager-linux", - src = "//common/manager:selenium-manager-linux", - out = "linux/selenium-manager", -) - -copy_file( - name = "manager-macos", - src = "//common/manager:selenium-manager-macos", - out = "macos/selenium-manager", -) - -copy_file( - name = "manager-windows", - src = "//common/manager:selenium-manager-windows", - out = "windows/selenium-manager.exe", -) diff --git a/java/src/org/openqa/selenium/manager/SeleniumManager.java b/java/src/org/openqa/selenium/manager/SeleniumManager.java index f0f0929049369..3dbf8acb74c5d 100644 --- a/java/src/org/openqa/selenium/manager/SeleniumManager.java +++ b/java/src/org/openqa/selenium/manager/SeleniumManager.java @@ -203,40 +203,49 @@ private synchronized Path getBinary() { if (binary == null) { try { Platform current = Platform.getCurrent(); - String folder = ""; + String os; String extension = ""; if (current.is(WINDOWS)) { extension = EXE; - folder = "windows"; + os = "windows"; } else if (current.is(MAC)) { - folder = "macos"; + os = "macos"; } else if (current.is(LINUX)) { - if (System.getProperty("os.arch").contains("arm") - || System.getProperty("os.arch").contains("aarch64")) { - throw new WebDriverException("Linux ARM is not supported by Selenium Manager"); - } else { - folder = "linux"; - } + os = "linux"; } else if (current.is(UNIX)) { LOG.warning( String.format( "Selenium Manager binary may not be compatible with %s; verify settings", current)); - folder = "linux"; + os = "linux"; } else { throw new WebDriverException("Unsupported platform: " + current); } + String osArch = System.getProperty("os.arch", "").toLowerCase(); + String arch; + if (osArch.contains("aarch64") || osArch.contains("arm64")) { + arch = "aarch64"; + } else if (osArch.contains("amd64") + || osArch.contains("x86_64") + || osArch.contains("x64")) { + arch = "x86_64"; + } else { + throw new WebDriverException( + "Selenium Manager does not ship a binary for " + osArch + " on " + os); + } + binary = getBinaryInCache(SELENIUM_MANAGER + extension); if (!Files.exists(binary)) { - String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension); + String binaryPathInJar = + String.format("/common/manager/%s-%s/%s%s", os, arch, SELENIUM_MANAGER, extension); try (InputStream inputStream = requireNonNull(getClass().getResourceAsStream(binaryPathInJar))) { Files.createDirectories(binary.getParent()); saveToFileSafely(inputStream, binary); } } - } catch (Exception e) { + } catch (IOException | NullPointerException e) { throw new WebDriverException("Unable to obtain Selenium Manager Binary", e); } } else if (!Files.exists(binary)) { diff --git a/javascript/selenium-webdriver/BUILD.bazel b/javascript/selenium-webdriver/BUILD.bazel index 1835ef5e3333c..a6c33cf1e80d4 100644 --- a/javascript/selenium-webdriver/BUILD.bazel +++ b/javascript/selenium-webdriver/BUILD.bazel @@ -83,9 +83,7 @@ npm_package( srcs = [ ":create-bidi-src", ":license", - ":manager-linux", - ":manager-macos", - ":manager-windows", + ":manager-bundle", ":prod-src-files", "//javascript/selenium-webdriver/lib/atoms:bidi-mutation-listener", "//javascript/selenium-webdriver/lib/atoms:find-elements", @@ -234,22 +232,40 @@ genrule( cmd = "cp $(locations //:license) $(@D)", ) -copy_file( - name = "manager-linux", - src = "//common/manager:selenium-manager-linux", - out = "bin/linux/selenium-manager", -) - -copy_file( - name = "manager-macos", - src = "//common/manager:selenium-manager-macos", - out = "bin/macos/selenium-manager", -) +[ + copy_file( + name = "manager-{}-{}".format(os_name, arch), + src = "//common/manager:selenium-manager-{}-{}".format(os_name, arch), + out = "bin/manager/{}-{}/selenium-manager{}".format( + os_name, + arch, + ".exe" if os_name == "windows" else "", + ), + is_executable = True, + ) + for (os_name, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -copy_file( - name = "manager-windows", - src = "//common/manager:selenium-manager-windows", - out = "bin/windows/selenium-manager.exe", +filegroup( + name = "manager-bundle", + srcs = select({ + "//common:windows_x86_64": [":manager-windows-x86_64"], + "//common:windows_aarch64": [":manager-windows-aarch64"], + "//common:macos_aarch64": [":manager-macos-aarch64"], + "//conditions:default": [ + ":manager-linux-aarch64", + ":manager-linux-x86_64", + ":manager-macos-aarch64", + ":manager-windows-aarch64", + ":manager-windows-x86_64", + ], + }), ) copy_to_bin( diff --git a/javascript/selenium-webdriver/common/seleniumManager.js b/javascript/selenium-webdriver/common/seleniumManager.js index 71e79616da2b1..e0eb350e8b92b 100644 --- a/javascript/selenium-webdriver/common/seleniumManager.js +++ b/javascript/selenium-webdriver/common/seleniumManager.js @@ -21,7 +21,7 @@ * Wrapper for getting information from the Selenium Manager binaries */ -const { platform } = require('node:process') +const { platform, arch } = require('node:process') const path = require('node:path') const fs = require('node:fs') const spawnSync = require('node:child_process').spawnSync @@ -35,18 +35,29 @@ let debugMessagePrinted = false * @returns {string} */ function getBinary() { - const directory = { + const os = { darwin: 'macos', win32: 'windows', cygwin: 'windows', linux: 'linux', }[platform] - const file = directory === 'windows' ? 'selenium-manager.exe' : 'selenium-manager' + if (os === undefined) { + throw new Error(`Unsupported platform: ${platform}`) + } + + const cpu = { + arm64: 'aarch64', + x64: 'x86_64', + }[arch] - let seleniumManagerBasePath = path.join(__dirname, '..', '/bin') + if (cpu === undefined) { + throw new Error(`Unsupported architecture: ${arch}`) + } - const filePath = process.env.SE_MANAGER_PATH || path.join(seleniumManagerBasePath, directory, file) + const file = os === 'windows' ? 'selenium-manager.exe' : 'selenium-manager' + const seleniumManagerBasePath = path.join(__dirname, '..', 'bin', 'manager') + const filePath = process.env.SE_MANAGER_PATH || path.join(seleniumManagerBasePath, `${os}-${cpu}`, file) if (!fs.existsSync(filePath)) { throw new Error(`Unable to obtain Selenium Manager at ${filePath}`) diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 68a9fd2c100bd..094144e2e0c5a 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -102,22 +102,40 @@ genrule( toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], ) -copy_file( - name = "manager-linux", - src = "//common/manager:selenium-manager-linux", - out = "selenium/webdriver/common/linux/selenium-manager", -) - -copy_file( - name = "manager-macos", - src = "//common/manager:selenium-manager-macos", - out = "selenium/webdriver/common/macos/selenium-manager", -) +[ + copy_file( + name = "manager-{}-{}".format(os_name, arch), + src = "//common/manager:selenium-manager-{}-{}".format(os_name, arch), + out = "selenium/webdriver/common/manager/{}-{}/selenium-manager{}".format( + os_name, + arch, + ".exe" if os_name == "windows" else "", + ), + is_executable = True, + ) + for (os_name, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -copy_file( - name = "manager-windows", - src = "//common/manager:selenium-manager-windows", - out = "selenium/webdriver/common/windows/selenium-manager.exe", +filegroup( + name = "manager-bundle", + srcs = select({ + "//common:windows_x86_64": [":manager-windows-x86_64"], + "//common:windows_aarch64": [":manager-windows-aarch64"], + "//common:macos_aarch64": [":manager-macos-aarch64"], + "//conditions:default": [ + ":manager-linux-aarch64", + ":manager-linux-x86_64", + ":manager-macos-aarch64", + ":manager-windows-aarch64", + ":manager-windows-x86_64", + ], + }), ) copy_file( @@ -307,9 +325,7 @@ py_library( ], ), data = [ - ":manager-linux", - ":manager-macos", - ":manager-windows", + ":manager-bundle", ] + [":create-cdp-srcs-" + n for n in BROWSER_VERSIONS], imports = ["."], visibility = ["//visibility:public"], @@ -588,9 +604,7 @@ pkg_files( "//rust:selenium_manager_srcs", ], excludes = [ - ":manager-linux", - ":manager-macos", - ":manager-windows", + ":manager-bundle", ], strip_prefix = strip_prefix.from_pkg(), ) diff --git a/py/selenium/webdriver/common/selenium_manager.py b/py/selenium/webdriver/common/selenium_manager.py index 9dfdbb052fe4c..6a094872f397e 100644 --- a/py/selenium/webdriver/common/selenium_manager.py +++ b/py/selenium/webdriver/common/selenium_manager.py @@ -87,20 +87,16 @@ def _get_binary() -> Path: elif compiled_path.is_file(): path = compiled_path else: - allowed = { - ("darwin", "any"): "macos/selenium-manager", - ("win32", "x86_64"): "windows/selenium-manager.exe", - ("cygwin", "x86_64"): "windows/selenium-manager.exe", - ("linux", "x86_64"): "linux/selenium-manager", - ("freebsd", "x86_64"): "linux/selenium-manager", - ("openbsd", "x86_64"): "linux/selenium-manager", + os_map = { + "darwin": "macos", + "win32": "windows", + "cygwin": "windows", + "linux": "linux", + "freebsd": "linux", + "openbsd": "linux", } - # some operating systems report x86-64 architecture as amd64/AMD64 platform_name = sys.platform - arch = "any" if platform_name == "darwin" else platform.machine().lower() - arch = "x86_64" if arch == "amd64" else arch - # in Python < 3.14, sys.platform appends version number to BSD platform names if platform_name.startswith("freebsd"): logger.warning( @@ -112,11 +108,20 @@ def _get_binary() -> Path: logger.warning("Selenium Manager binary may not be compatible with OpenBSD; verify settings") platform_name = "openbsd" - location = allowed.get((platform_name, arch)) - if location is None: - raise WebDriverException(f"Unsupported platform/architecture combination: {sys.platform}/{arch}") + os_name = os_map.get(platform_name) + if os_name is None: + raise WebDriverException(f"Unsupported platform: {sys.platform}") + + machine = platform.machine().lower() + if machine in ("amd64", "x86_64", "x64"): + arch = "x86_64" + elif machine in ("aarch64", "arm64"): + arch = "aarch64" + else: + raise WebDriverException(f"Unsupported architecture: {machine}") - path = Path(__file__).parent.joinpath(location) + extension = ".exe" if os_name == "windows" else "" + path = Path(__file__).parent.joinpath("manager", f"{os_name}-{arch}", f"selenium-manager{extension}") if path is None or not path.is_file(): raise WebDriverException(f"Unable to obtain working Selenium Manager binary; {path}") diff --git a/py/test/unit/selenium/webdriver/common/selenium_manager_tests.py b/py/test/unit/selenium/webdriver/common/selenium_manager_tests.py index 7351f10163161..e70bc65d0b2a1 100644 --- a/py/test/unit/selenium/webdriver/common/selenium_manager_tests.py +++ b/py/test/unit/selenium/webdriver/common/selenium_manager_tests.py @@ -53,43 +53,61 @@ def test_uses_environment_variable(monkeypatch): def test_uses_windows(monkeypatch): monkeypatch.setattr(sys, "platform", "win32") monkeypatch.setattr("platform.machine", lambda: "AMD64") + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) binary = SeleniumManager()._get_binary() project_root = Path(selenium.__file__).parent.parent - assert binary == project_root.joinpath("selenium/webdriver/common/windows/selenium-manager.exe") + assert binary == project_root.joinpath("selenium/webdriver/common/manager/windows-x86_64/selenium-manager.exe") def test_uses_windows_arm64(monkeypatch): monkeypatch.setattr(sys, "platform", "win32") monkeypatch.setattr("platform.machine", lambda: "ARM64") - with pytest.raises(WebDriverException, match="Unsupported platform/architecture combination: win32/arm64"): - SeleniumManager()._get_binary() + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) + binary = SeleniumManager()._get_binary() + project_root = Path(selenium.__file__).parent.parent + assert binary == project_root.joinpath("selenium/webdriver/common/manager/windows-aarch64/selenium-manager.exe") def test_uses_linux(monkeypatch): monkeypatch.setattr(sys, "platform", "linux") monkeypatch.setattr("platform.machine", lambda: "x86_64") + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) binary = SeleniumManager()._get_binary() project_root = Path(selenium.__file__).parent.parent - assert binary == project_root.joinpath("selenium/webdriver/common/linux/selenium-manager") + assert binary == project_root.joinpath("selenium/webdriver/common/manager/linux-x86_64/selenium-manager") def test_uses_linux_arm64(monkeypatch): monkeypatch.setattr(sys, "platform", "linux") monkeypatch.setattr("platform.machine", lambda: "arm64") - with pytest.raises(WebDriverException, match="Unsupported platform/architecture combination: linux/arm64"): - SeleniumManager()._get_binary() + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) + binary = SeleniumManager()._get_binary() + project_root = Path(selenium.__file__).parent.parent + assert binary == project_root.joinpath("selenium/webdriver/common/manager/linux-aarch64/selenium-manager") def test_uses_mac(monkeypatch): monkeypatch.setattr(sys, "platform", "darwin") + monkeypatch.setattr("platform.machine", lambda: "arm64") + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) + binary = SeleniumManager()._get_binary() + project_root = Path(selenium.__file__).parent.parent + assert binary == project_root.joinpath("selenium/webdriver/common/manager/macos-aarch64/selenium-manager") + + +def test_uses_mac_intel(monkeypatch): + monkeypatch.setattr(sys, "platform", "darwin") + monkeypatch.setattr("platform.machine", lambda: "x86_64") + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) binary = SeleniumManager()._get_binary() project_root = Path(selenium.__file__).parent.parent - assert binary == project_root.joinpath("selenium/webdriver/common/macos/selenium-manager") + assert binary == project_root.joinpath("selenium/webdriver/common/manager/macos-x86_64/selenium-manager") def test_uses_freebsd(monkeypatch, caplog): monkeypatch.setattr(sys, "platform", "freebsd15") monkeypatch.setattr("platform.machine", lambda: "amd64") + monkeypatch.setattr(Path, "is_file", lambda self: "manager" in self.parts) root = logging.getLogger() caplog_handler = caplog.handler old_handlers = root.handlers[:] @@ -97,7 +115,7 @@ def test_uses_freebsd(monkeypatch, caplog): try: binary = SeleniumManager()._get_binary() project_root = Path(selenium.__file__).parent.parent - assert binary == project_root.joinpath("selenium/webdriver/common/linux/selenium-manager") + assert binary == project_root.joinpath("selenium/webdriver/common/manager/linux-x86_64/selenium-manager") assert "Selenium Manager binary may not be compatible with FreeBSD" in caplog.text finally: root.handlers = old_handlers @@ -115,7 +133,7 @@ def test_errors_if_invalid_os(monkeypatch): monkeypatch.setattr("platform.machine", lambda: "invalid") with pytest.raises(WebDriverException) as excinfo: SeleniumManager()._get_binary() - assert "Unsupported platform/architecture combination" in str(excinfo.value) + assert "Unsupported architecture" in str(excinfo.value) def test_error_if_invalid_env_path(monkeypatch): diff --git a/rb/BUILD.bazel b/rb/BUILD.bazel index abd2dba9eda84..0b54f25bd8c4f 100644 --- a/rb/BUILD.bazel +++ b/rb/BUILD.bazel @@ -11,22 +11,40 @@ package(default_visibility = ["//:__subpackages__"]) GITHUB_PACKAGES_HOST = "https://rubygems.pkg.github.com/SeleniumHQ" -copy_file( - name = "manager-linux", - src = "//common/manager:selenium-manager-linux", - out = "bin/linux/selenium-manager", -) - -copy_file( - name = "manager-macos", - src = "//common/manager:selenium-manager-macos", - out = "bin/macos/selenium-manager", -) +[ + copy_file( + name = "manager-{}-{}".format(os_name, arch), + src = "//common/manager:selenium-manager-{}-{}".format(os_name, arch), + out = "bin/manager/{}-{}/selenium-manager{}".format( + os_name, + arch, + ".exe" if os_name == "windows" else "", + ), + is_executable = True, + ) + for (os_name, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -copy_file( - name = "manager-windows", - src = "//common/manager:selenium-manager-windows", - out = "bin/windows/selenium-manager.exe", +filegroup( + name = "manager-bundle", + srcs = select({ + "//common:windows_x86_64": [":manager-windows-x86_64"], + "//common:windows_aarch64": [":manager-windows-aarch64"], + "//common:macos_aarch64": [":manager-macos-aarch64"], + "//conditions:default": [ + ":manager-linux-aarch64", + ":manager-linux-x86_64", + ":manager-macos-aarch64", + ":manager-windows-aarch64", + ":manager-windows-x86_64", + ], + }), ) select_file( @@ -63,9 +81,7 @@ rb_gem_build( "CHANGES", "README.md", ":license", - ":manager-linux", - ":manager-macos", - ":manager-windows", + ":manager-bundle", ":notice", ], gemspec = "selenium-webdriver.gemspec", diff --git a/rb/lib/selenium/webdriver/common/selenium_manager.rb b/rb/lib/selenium/webdriver/common/selenium_manager.rb index 4a8603b6584b0..6444cb42d4d67 100644 --- a/rb/lib/selenium/webdriver/common/selenium_manager.rb +++ b/rb/lib/selenium/webdriver/common/selenium_manager.rb @@ -31,7 +31,7 @@ class << self attr_writer :bin_path def bin_path - @bin_path ||= '../../../../../bin' + @bin_path ||= '../../../../../bin/manager' end # @param [Array] arguments what gets sent to to Selenium Manager binary. @@ -71,21 +71,40 @@ def run(*command) def platform_location directory = File.expand_path(bin_path, __FILE__) + os = platform_os + arch = platform_arch(os) + extension = os == 'windows' ? '.exe' : '' + "#{directory}/#{os}-#{arch}/selenium-manager#{extension}" + end + + def platform_os if Platform.windows? - "#{directory}/windows/selenium-manager.exe" + 'windows' elsif Platform.mac? - "#{directory}/macos/selenium-manager" + 'macos' elsif Platform.linux? - "#{directory}/linux/selenium-manager" + 'linux' elsif Platform.unix? WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix', id: %i[selenium_manager unix_binary]) - "#{directory}/linux/selenium-manager" + 'linux' else raise Error::WebDriverError, "unsupported platform: #{Platform.os}" end end + def platform_arch(_os_name) + cpu = RbConfig::CONFIG['host_cpu'].to_s.downcase + case cpu + when 'x86_64', 'amd64', 'x64' + 'x86_64' + when 'aarch64', 'arm64' + 'aarch64' + else + raise Error::WebDriverError, "unsupported architecture: #{cpu}" + end + end + def execute_command(*command) WebDriver.logger.debug("Executing Process #{command}", id: :selenium_manager) diff --git a/rb/spec/integration/selenium/webdriver/BUILD.bazel b/rb/spec/integration/selenium/webdriver/BUILD.bazel index 9fbb393ab8ecb..2ee9d70469748 100644 --- a/rb/spec/integration/selenium/webdriver/BUILD.bazel +++ b/rb/spec/integration/selenium/webdriver/BUILD.bazel @@ -9,9 +9,7 @@ rb_library( "spec_support.rb", ] + glob(["spec_support/**/*"]), data = [ - "//rb:manager-linux", - "//rb:manager-macos", - "//rb:manager-windows", + "//rb:manager-bundle", ], visibility = ["//rb/spec:__subpackages__"], deps = [ diff --git a/rb/spec/unit/selenium/webdriver/common/selenium_manager_spec.rb b/rb/spec/unit/selenium/webdriver/common/selenium_manager_spec.rb index 4ed72fa9fa2a2..157dccb31f61d 100644 --- a/rb/spec/unit/selenium/webdriver/common/selenium_manager_spec.rb +++ b/rb/spec/unit/selenium/webdriver/common/selenium_manager_spec.rb @@ -35,27 +35,40 @@ module WebDriver end it 'detects Windows' do - allow(Platform).to receive(:assert_executable).with(a_string_ending_with('/windows/selenium-manager.exe')) - .and_return(true) + allow(RbConfig::CONFIG).to receive(:[]).with('host_cpu').and_return('x86_64') + allow(Platform).to receive(:assert_executable) + .with(a_string_ending_with('windows-x86_64/selenium-manager.exe')) + .and_return(true) allow(Platform).to receive(:windows?).and_return(true) - expect(described_class.send(:binary)).to match(%r{/windows/selenium-manager\.exe$}) + expect(described_class.send(:binary)).to match(%r{windows-x86_64/selenium-manager\.exe$}) + end + + it 'detects Mac (aarch64)' do + allow(RbConfig::CONFIG).to receive(:[]).with('host_cpu').and_return('arm64') + allow(Platform).to receive(:assert_executable).with(a_string_ending_with('macos-aarch64/selenium-manager')) + .and_return(true) + allow(Platform).to receive_messages(windows?: false, mac?: true) + + expect(described_class.send(:binary)).to match(%r{macos-aarch64/selenium-manager$}) end - it 'detects Mac' do - allow(Platform).to receive(:assert_executable).with(a_string_ending_with('/macos/selenium-manager')) + it 'detects Mac (x86_64)' do + allow(RbConfig::CONFIG).to receive(:[]).with('host_cpu').and_return('x86_64') + allow(Platform).to receive(:assert_executable).with(a_string_ending_with('macos-x86_64/selenium-manager')) .and_return(true) allow(Platform).to receive_messages(windows?: false, mac?: true) - expect(described_class.send(:binary)).to match(%r{/macos/selenium-manager$}) + expect(described_class.send(:binary)).to match(%r{macos-x86_64/selenium-manager$}) end it 'detects Linux' do - allow(Platform).to receive(:assert_executable).with(a_string_ending_with('/linux/selenium-manager')) + allow(RbConfig::CONFIG).to receive(:[]).with('host_cpu').and_return('x86_64') + allow(Platform).to receive(:assert_executable).with(a_string_ending_with('linux-x86_64/selenium-manager')) .and_return(true) allow(Platform).to receive_messages(windows?: false, mac?: false, linux?: true) - expect(described_class.send(:binary)).to match(%r{/linux/selenium-manager$}) + expect(described_class.send(:binary)).to match(%r{linux-x86_64/selenium-manager$}) end it 'errors if cannot find' do diff --git a/rust/BUILD.bazel b/rust/BUILD.bazel index 2e6d3d7455fc6..85e3d3263181a 100644 --- a/rust/BUILD.bazel +++ b/rust/BUILD.bazel @@ -1,4 +1,8 @@ +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") +load("@aspect_bazel_lib//lib:output_files.bzl", "output_files") +load("@aspect_bazel_lib//lib:transitions.bzl", "platform_transition_binary") load("@crates//:defs.bzl", "aliases", "all_crate_deps") +load("//common:defs.bzl", "copy_file") load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test", "rustfmt_config") rustfmt_config( @@ -7,74 +11,214 @@ rustfmt_config( # We want the release versions of Selenium to include the prebuilt # binaries, but if we're doing day-to-day dev work, then we should -# use a local build, unless on we're on Windows, where for some -# reason we're not able to build locally. +# use a local build. The Windows build is cross-compiled via rules_rs +# so it is no longer necessary to fall back to a downloaded artifact +# on non-Windows hosts. # -# We tag the compiled versions as `manual` so that when we do a -# `bazel build //...` we don't do any additional work +# Each (OS, arch) variant cross-compiles directly via rules_rs +# +# Linux targets use musl rather than glibc because Rust's prebuilt glibc +# stdlib hard-references `-lgcc_s`, which the @llvm toolchain doesn't ship. +# musl produces a fully self-contained binary that runs on any Linux +# distribution. +# +# Windows targets use the LLVM-based MinGW (gnullvm) ABI rather than MSVC +# because the @llvm toolchain ships everything needed to produce a PE +# binary without a separately-provisioned Windows SDK. +[ + platform_transition_binary( + name = "selenium-manager-{os}-{arch}".format( + arch = arch, + os = os, + ), + # The underlying rust_binary is named `_selenium-manager-bin` so the + # public `selenium-manager` label can be a host-dispatching alias. + # `basename` keeps the produced file's name as plain `selenium-manager` + # so consumers (and the bundle paths) don't see the `-bin` suffix. + basename = "selenium-manager.exe" if os == "windows" else "selenium-manager", + binary = ":_selenium-manager-bin", + tags = [ + "manual", + ], + target_platform = "@rules_rs//rs/platforms:{}".format(triple), + visibility = [ + "//common/manager:__pkg__", + ], + ) + for (os, arch, triple) in [ + ("linux", "aarch64", "aarch64-unknown-linux-musl"), + ("linux", "x86_64", "x86_64-unknown-linux-musl"), + ("macos", "aarch64", "aarch64-apple-darwin"), + ("macos", "x86_64", "x86_64-apple-darwin"), + ("windows", "aarch64", "aarch64-pc-windows-gnullvm"), + ("windows", "x86_64", "x86_64-pc-windows-gnullvm"), + ] +] -# Start with the variants for each platform +# Picks the cross-built binary that matches the host. No default branch +# on purpose: hosts not in the explicit list should fail loudly at build +# time rather than silently fall back to a binary that cannot run. alias( - name = "selenium-manager-windows", + name = "selenium-manager", actual = select({ - "//common:windows": ":selenium-manager", - "//conditions:default": "@download_sm_windows//file", + "//common:linux_aarch64": ":selenium-manager-linux-aarch64", + "//common:linux_x86_64": ":selenium-manager-linux-x86_64", + "//common:macos_aarch64": ":selenium-manager-macos-aarch64", + "//common:macos_x86_64": ":selenium-manager-macos-x86_64", + # A Windows host is pinned to the gnullvm target platform (see the + # --platforms setting in .bazelrc), so the native rust_binary already + # builds the correct gnullvm PE binary. Using it directly avoids an + # unnecessary platform_transition_binary (and its -ST- exec config) + # on the host's own architecture. + "//common:windows_aarch64": ":_selenium-manager-bin", + "//common:windows_x86_64": ":_selenium-manager-bin", }), - tags = [ - "manual", - ], - visibility = [ - "//common/manager:__pkg__", - ], + visibility = ["//visibility:public"], ) -alias( - name = "selenium-manager-macos", - actual = select({ - "//common:macos": ":selenium-manager", - "//conditions:default": "@download_sm_macos//file", - }), - tags = [ - "manual", - ], - visibility = [ - "//common/manager:__pkg__", - ], -) +# Tree of every cross-built binary at `-/selenium-manager[.exe]`. +# Language packagers ship this whole tree and the binding's runtime picks +# the correct binary based on host detection. +# +# `platform_transition_binary` forwards the underlying rust_binary in its +# DefaultInfo alongside the runnable wrapper symlink, so feeding the +# transition target straight into copy_to_directory collides on the +# original `rust/selenium-manager` path. `output_files` filters the +# DefaultInfo down to just the wrapper symlink, which lives at the +# arch-specific path the bundle layout depends on. +[ + output_files( + name = "_bundle_member_{}_{}".format(os, arch), + paths = ["rust/selenium-manager-{}-{}/selenium-manager{}".format( + os, + arch, + ".exe" if os == "windows" else "", + )], + tags = ["manual"], + target = ":selenium-manager-{}-{}".format(os, arch), + visibility = ["//common/manager:__pkg__"], + ) + for (os, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("macos", "x86_64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -alias( - name = "selenium-manager-linux", - actual = select({ - "//common:linux": ":selenium-manager", - "//conditions:default": "@download_sm_linux//file", - }), - tags = [ - "manual", - ], - visibility = [ - "//common/manager:__pkg__", - ], +# Stub script used in the Windows-host bundle for OS/arch entries we +# can't produce on a Windows runner. Cross-compiling through rules_rs's +# LLVM toolchain on Windows hits a `process_wrapper` / proc-macro exec +# config divergence that breaks the build; on Windows we fall back to a +# native rust_binary for the host's arch and stub the rest. Release +# bundles are produced on Linux, where all five entries are real. +genrule( + name = "_selenium-manager-stub", + outs = ["_selenium-manager-stub.sh"], + cmd = "\n".join([ + "cat > $@ <<'EOF'", + "#!/bin/sh", + "echo 'selenium-manager: binary not built on this host' >&2", + "exit 1", + "EOF", + "chmod +x $@", + ]), + executable = True, + visibility = ["//common/manager:__pkg__"], ) -filegroup( - name = "selenium-manager-dev", - srcs = [ - ":selenium-manager-linux", - ":selenium-manager-macos", - ":selenium-manager-windows", - ], - tags = [ - "manual", - ], - visibility = [ - "//common/manager:__subpackages__", +# Native-host bundle layouts. Each per-(os, arch) member ends up at +# `rust/_bundle_native_/-/selenium-manager[.exe]` so the +# bundle's `root_paths` can map straight to the public layout. +[ + copy_file( + name = "_bundle_native_{host_arch}_{os}_{arch}".format( + arch = arch, + host_arch = host_arch, + os = os, + ), + src = ( + ":_selenium-manager-bin" if (os, arch) == ("windows", host_arch) else ":_selenium-manager-stub" + ), + out = "_bundle_native_windows_{host_arch}/{os}-{arch}/selenium-manager{ext}".format( + arch = arch, + ext = ".exe" if os == "windows" else "", + host_arch = host_arch, + os = os, + ), + is_executable = True, + ) + for host_arch in ("aarch64", "x86_64") + for (os, arch) in [ + ("linux", "aarch64"), + ("linux", "x86_64"), + ("macos", "aarch64"), + ("macos", "x86_64"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] + +copy_to_directory( + name = "selenium-manager-bundle", + # On Windows hosts cross-compiling through the rules_rs LLVM toolchain + # is currently broken, so we ship only a native build for the host's + # architecture and stub the rest. Linux/macOS hosts (and release + # builds) keep the full five-platform cross-compile. + srcs = select({ + "//common:windows_x86_64": [ + ":_bundle_native_x86_64_linux_aarch64", + ":_bundle_native_x86_64_linux_x86_64", + ":_bundle_native_x86_64_macos_aarch64", + ":_bundle_native_x86_64_macos_x86_64", + ":_bundle_native_x86_64_windows_aarch64", + ":_bundle_native_x86_64_windows_x86_64", + ], + "//common:windows_aarch64": [ + ":_bundle_native_aarch64_linux_aarch64", + ":_bundle_native_aarch64_linux_x86_64", + ":_bundle_native_aarch64_macos_aarch64", + ":_bundle_native_aarch64_macos_x86_64", + ":_bundle_native_aarch64_windows_aarch64", + ":_bundle_native_aarch64_windows_x86_64", + ], + "//conditions:default": [ + ":_bundle_member_linux_aarch64", + ":_bundle_member_linux_x86_64", + ":_bundle_member_macos_aarch64", + ":_bundle_member_macos_x86_64", + ":_bundle_member_windows_aarch64", + ":_bundle_member_windows_x86_64", + ], + }), + replace_prefixes = { + "selenium-manager-linux-aarch64/": "linux-aarch64/", + "selenium-manager-linux-x86_64/": "linux-x86_64/", + "selenium-manager-macos-aarch64/": "macos-aarch64/", + "selenium-manager-macos-x86_64/": "macos-x86_64/", + "selenium-manager-windows-aarch64/": "windows-aarch64/", + "selenium-manager-windows-x86_64/": "windows-x86_64/", + }, + # Longest matching prefix wins, so the native-host roots take priority + # over the generic "rust" root. + root_paths = [ + "rust/_bundle_native_windows_x86_64", + "rust/_bundle_native_windows_aarch64", + "rust", ], + tags = ["manual"], + visibility = ["//common/manager:__pkg__"], ) rust_binary( - # Yes, this name is very similar to the library. Note the dash - # instead of an underscore - name = "selenium-manager", + # Internal native rust_binary. The public `selenium-manager` label is + # the host-dispatching alias above, which delegates to the matching + # platform_transition_binary. Untransitioned builds (the integration + # tests, `bazel run //rust:_selenium-manager-bin`) pick this up + # directly with the host's default Rust toolchain. + name = "_selenium-manager-bin", srcs = ["src/main.rs"], aliases = aliases(), edition = "2024", diff --git a/rust/tests/BUILD.bazel b/rust/tests/BUILD.bazel index 3bf6c7834529c..a1d4e39f961fc 100644 --- a/rust/tests/BUILD.bazel +++ b/rust/tests/BUILD.bazel @@ -11,11 +11,11 @@ rust_test_suite( srcs = glob(["**/*_tests.rs"]), aliases = aliases(), data = [ - "//rust:selenium-manager", + "//rust:_selenium-manager-bin", ], edition = "2024", rustc_env = { - "CARGO_BIN_EXE_selenium-manager": "rust/selenium-manager", + "CARGO_BIN_EXE_selenium-manager": "rust/_selenium-manager-bin", }, shared_srcs = glob( ["**/*.rs"], diff --git a/third_party/bazel/BUILD.bazel b/third_party/bazel/BUILD.bazel index e69de29bb2d1d..d518110449e66 100644 --- a/third_party/bazel/BUILD.bazel +++ b/third_party/bazel/BUILD.bazel @@ -0,0 +1 @@ +exports_files(glob(["*.patch"])) diff --git a/third_party/bazel/llvm_libgcc_s_universal_scope.patch b/third_party/bazel/llvm_libgcc_s_universal_scope.patch new file mode 100644 index 0000000000000..0b09151f0b2aa --- /dev/null +++ b/third_party/bazel/llvm_libgcc_s_universal_scope.patch @@ -0,0 +1,10 @@ +--- a/config/defs.bzl ++++ b/config/defs.bzl +@@ -143,6 +143,7 @@ def config_settings(): + bool_flag( + name = "experimental_stub_libgcc_s", + build_setting_default = False, ++ scope = "universal", + ) + + for sanitizer in SANITIZERS: diff --git a/third_party/bazel/windows_link_readme_case.patch b/third_party/bazel/windows_link_readme_case.patch new file mode 100644 index 0000000000000..b2a77bf457da8 --- /dev/null +++ b/third_party/bazel/windows_link_readme_case.patch @@ -0,0 +1,6 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -1,3 +1,2 @@ +-#![doc = include_str!("../readme.md")] + #![no_std] + diff --git a/third_party/bazel/windows_targets_readme_case.patch b/third_party/bazel/windows_targets_readme_case.patch new file mode 100644 index 0000000000000..b2a77bf457da8 --- /dev/null +++ b/third_party/bazel/windows_targets_readme_case.patch @@ -0,0 +1,6 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -1,3 +1,2 @@ +-#![doc = include_str!("../readme.md")] + #![no_std] +