diff --git a/.bazelrc b/.bazelrc index 53905096b2608..a4909c40aa6a8 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,6 +10,13 @@ try-import %workspace%/.bazelrc.windows.local common --lockfile_mode=off +# Skip Bazel's auto-detection of the host C/C++ toolchain. We register the +# LLVM hermetic toolchain explicitly in MODULE.bazel and don't want +# rules_cc's auto-detected `local_config_cc` to influence toolchain +# resolution on developer hosts or CI runners. +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 @@ -78,7 +85,10 @@ build:windows --host_per_file_copt=external/protobuf\\+.*@/w # For build stamping common --enable_platform_specific_config common:linux --host_platform=//:local_linux_gnu -common:windows --host_platform=//:local_windows_gnullvm +# 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_msvc build:linux --workspace_status_command=scripts/build-info.sh build:macos --workspace_status_command=scripts/build-info.sh build:windows --workspace_status_command="powershell.exe scripts/build-info.ps1" diff --git a/BUILD.bazel b/BUILD.bazel index b944e4cd69201..19f52be7e9bbb 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -129,7 +129,33 @@ platform( platform( name = "local_windows_gnullvm", constraint_values = [ - "@rules_rs//rs/platforms/constraints:windows_gnullvm", + "@llvm//constraints/windows/abi:gnullvm", + # Rust's `x86_64-pc-windows-gnullvm` and `aarch64-pc-windows-gnullvm` + # toolchains both link against MSVCRT (see rustc's `windows_gnullvm` + # spec), and rules_rs encodes that as a `windows/crt:msvcrt` + # constraint on the toolchain. Without it here, no rust toolchain + # matches the host platform on Windows and `bazel test //rust/...` + # fails with "No matching toolchains found". + "@llvm//constraints/windows/crt:msvcrt", + ], + parents = ["@platforms//host"], +) + +# Native Windows host platform. Used as `--host_platform` on Windows +# runners so native rust builds go through `x86_64-pc-windows-msvc` (which +# the Windows runners have installed as part of Visual Studio) rather +# than `x86_64-pc-windows-gnullvm`. The gnullvm path drags in LLVM's +# bootstrap-process-wrapper Starlark transition, which puts +# `process_wrapper` in a different exec config than proc-macro deps and +# trips rustc with `can't find crate for time_macros / scroll_derive` +# style errors. Cross-compile targets in //rust still transition to the +# gnullvm rules_rs platforms so non-Windows hosts can produce PE binaries +# without a Windows SDK. +platform( + name = "local_windows_msvc", + constraint_values = [ + "@llvm//constraints/windows/abi:msvc", + "@llvm//constraints/windows/crt:msvcrt", ], parents = ["@platforms//host"], ) diff --git a/MODULE.bazel b/MODULE.bazel index 86bbc49049bd3..73d7baad15895 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,7 +10,7 @@ bazel_dep(name = "bazel_features", version = "1.47.1") bazel_dep(name = "bazel_skylib", version = "1.9.0") bazel_dep(name = "buildifier_prebuilt", version = "8.5.1.2") bazel_dep(name = "contrib_rules_jvm", version = "0.33.0") -bazel_dep(name = "llvm", version = "0.7.9") +bazel_dep(name = "llvm", version = "0.8.0") bazel_dep(name = "package_metadata", version = "0.0.10") bazel_dep(name = "platforms", version = "1.1.0") @@ -26,6 +26,23 @@ 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.0", +) + 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") @@ -40,7 +57,17 @@ bazel_dep(name = "rules_pkg", version = "1.2.0") bazel_dep(name = "rules_python", version = "1.9.0") 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.61") +bazel_dep(name = "rules_rs", version = "0.0.76") + +# 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", @@ -394,7 +421,6 @@ 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") llvm_toolchains.exec( arch = "x86_64", @@ -404,6 +430,18 @@ llvm_toolchains.exec( arch = "aarch64", os = "windows", ) +llvm_toolchains.exec( + arch = "aarch64", + os = "macos", +) +llvm_toolchains.exec( + arch = "x86_64", + os = "linux", +) +llvm_toolchains.exec( + arch = "aarch64", + os = "linux", +) use_repo(llvm_toolchains, "llvm_toolchains") register_toolchains("@llvm_toolchains//:all") @@ -415,25 +453,63 @@ 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 a `README.md` (uppercase) but their source code uses +# `include_str!("../readme.md")`. The mismatch is invisible on macOS's +# case-insensitive default filesystem but breaks any case-sensitive host +# (Linux, RBE). Patch the source so the case matches what the archive +# actually contains. +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"], + # Only the 0.53 line has this case mismatch; 0.52.x ships a lowercase + # readme.md that matches the include_str! call. + 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..e04f8b63ba41a 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -41,6 +41,24 @@ 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"), + ("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 e38ecafdb98e6..f4010acfb4957 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,26 +11,61 @@ 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"), + ("windows", "aarch64"), + ("windows", "x86_64"), + ] +] -alias( - name = "selenium-manager-windows", - actual = select({ - "//common:use_pinned_browser": "@download_sm_windows//file", - "//conditions:default": "//rust:selenium-manager-windows", - }), +filegroup( + name = "selenium-manager-files", + srcs = [ + ":selenium-manager-linux-aarch64", + ":selenium-manager-linux-x86_64", + ":selenium-manager-macos-aarch64", + ":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 3eb9ed6cd4eb4..e9ca9e66ff053 100644 --- a/dotnet/src/webdriver/BUILD.bazel +++ b/dotnet/src/webdriver/BUILD.bazel @@ -147,24 +147,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__"], ) @@ -172,9 +188,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..5d23be574556d 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,35 @@ 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}"), + }; + + if (platform == SupportedPlatform.MacOS && processArchitecture != Architecture.Arm64) + { + throw new PlatformNotSupportedException( + "Selenium Manager only ships an arm64 binary for macOS"); + } +#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 +154,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 1232379393662..aa932bafb3bfe 100644 --- a/dotnet/src/webdriver/Selenium.WebDriver.csproj +++ b/dotnet/src/webdriver/Selenium.WebDriver.csproj @@ -59,7 +59,7 @@ - + @@ -68,9 +68,11 @@ - - - + + + + + diff --git a/dotnet/src/webdriver/Selenium.WebDriver.nuspec b/dotnet/src/webdriver/Selenium.WebDriver.nuspec index bcb251bb86488..77ffff48e7bfa 100644 --- a/dotnet/src/webdriver/Selenium.WebDriver.nuspec +++ b/dotnet/src/webdriver/Selenium.WebDriver.nuspec @@ -57,9 +57,11 @@ - - - + + + + + diff --git a/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props b/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props index 31e59250fd6d3..22195dbedb86e 100644 --- a/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props +++ b/dotnet/src/webdriver/assets/nuget/build/Selenium.WebDriver.props @@ -4,22 +4,36 @@ - - 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 diff --git a/dotnet/test/webdriver/BUILD.bazel b/dotnet/test/webdriver/BUILD.bazel index 1e82ba627c782..f367a2457e61d 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 e042b500c281f..bfabe22e28e2f 100644 --- a/java/src/org/openqa/selenium/manager/SeleniumManager.java +++ b/java/src/org/openqa/selenium/manager/SeleniumManager.java @@ -195,40 +195,53 @@ 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); + } + + if (os.equals("macos") && !arch.equals("aarch64")) { + throw new WebDriverException("Selenium Manager only ships an aarch64 binary for macOS"); + } + 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 bac28efe76fed..17d194d48c143 100644 --- a/javascript/selenium-webdriver/BUILD.bazel +++ b/javascript/selenium-webdriver/BUILD.bazel @@ -54,9 +54,7 @@ npm_package( name = "selenium-webdriver", srcs = [ ":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", @@ -205,22 +203,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..8e43b3eacefbd 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,33 @@ 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}`) + } + + if (os === 'macos' && cpu !== 'aarch64') { + throw new Error('Selenium Manager only ships an aarch64 binary for macOS') + } - 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 7c8df0f693f05..cc271f561039d 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( @@ -299,9 +317,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"], @@ -495,9 +511,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..48250ccbc621d 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,23 @@ 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}") + + if os_name == "macos" and arch != "aarch64": + raise WebDriverException("Selenium Manager only ships an aarch64 binary for macOS") - 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..cb0f5fb847ffd 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,52 @@ 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/macos/selenium-manager") + assert binary == project_root.joinpath("selenium/webdriver/common/manager/macos-aarch64/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 +106,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 +124,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 7b0dc2df53de8..5562dd7c898d8 100644 --- a/rb/BUILD.bazel +++ b/rb/BUILD.bazel @@ -12,22 +12,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( @@ -64,9 +82,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..75a96fb465fb5 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,45 @@ 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 + arch = case cpu + when 'x86_64', 'amd64', 'x64' + 'x86_64' + when 'aarch64', 'arm64' + 'aarch64' + else + raise Error::WebDriverError, "unsupported architecture: #{cpu}" + end + if os_name == 'macos' && arch != 'aarch64' + raise Error::WebDriverError, 'Selenium Manager only ships an aarch64 binary for macOS' + end + + arch + 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 f786bb224308f..1ea42ec040b5a 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 9ccf65d1974f5..0195f49594c26 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,31 @@ 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' do - allow(Platform).to receive(:assert_executable).with(a_string_ending_with('/macos/selenium-manager')) + 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/selenium-manager$}) + expect(described_class.send(:binary)).to match(%r{macos-aarch64/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 4125c84fe00dc..49fe74e25c18a 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,79 +11,210 @@ 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"), + ("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 (e.g. x86_64 macOS) 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:windows_aarch64": ":selenium-manager-windows-aarch64", + "//common:windows_x86_64": ":selenium-manager-windows-x86_64", }), - 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"), + ("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"), + ("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_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_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_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-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", version = "0.4.44", - visibility = ["//visibility:public"], + visibility = [ + "//common/manager:__pkg__", + "//rust:__subpackages__", + ], deps = [ ":selenium_manager", ] + all_crate_deps(normal = True), 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..2991897e960da --- /dev/null +++ b/third_party/bazel/windows_link_readme_case.patch @@ -0,0 +1,7 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -1,3 +1,3 @@ +-#![doc = include_str!("../readme.md")] ++#![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..2991897e960da --- /dev/null +++ b/third_party/bazel/windows_targets_readme_case.patch @@ -0,0 +1,7 @@ +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -1,3 +1,3 @@ +-#![doc = include_str!("../readme.md")] ++#![doc = include_str!("../README.md")] + #![no_std] +