Skip to content

Commit f1cc57a

Browse files
[rust] Allow cross-compilation of selenium-manager on all platforms
Upgrades the Bazel Rust toolchain stack to enable hermetic cross-compilation of selenium-manager for every supported OS/arch combination: Build system changes: - Upgrade llvm 0.7.9 → 0.8.0 and rules_rs 0.0.61 → 0.0.76 - Register hermetic LLVM exec toolchains for Linux, macOS, and Windows so all C/C++ compilation in the exec config (process_wrapper, build scripts) is version-pinned and reproducible across CI runners - Add `experimental_stub_libgcc_s` patch for llvm 0.8.0 so the flag propagates to exec-config builds (fixes process_wrapper link on Linux) - Disable local_config_cc auto-detection globally; LLVM toolchain now covers all platforms (macOS is viable because the ring fix removed aws-lc-sys) - Windows uses the gnullvm ABI — the hermetic LLVM package produces correct PE binaries without a separately-provisioned Visual Studio installation - Add bzip2, xz, zstd BCR deps so *-sys crates use the hermetic CC toolchain - Patch windows-link and windows-targets to remove include_str! doc attrs that fail in Bazel's source-only sandbox Cross-compilation targets in rust/BUILD.bazel: - platform_transition_binary for each (os, arch) pair using musl on Linux (self-contained, no glibc dependency) and gnullvm on Windows - selenium-manager-bundle collects all variants; stub binaries stand in for targets that are skipped on the current host Binding updates (Java, Python, Ruby, .NET, JS): - All selenium_manager helpers updated to probe per-(os, arch) paths from the bundle and fall back to the legacy single-file location - .NET nuspec/csproj updated with per-RID native asset paths
1 parent 2eaf118 commit f1cc57a

30 files changed

Lines changed: 775 additions & 273 deletions

File tree

.bazelrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ try-import %workspace%/.bazelrc.windows.local
1010

1111
common --lockfile_mode=off
1212

13+
# Skip Bazel's auto-detection of the host C/C++ toolchain. We register the
14+
# hermetic LLVM toolchain explicitly in MODULE.bazel for all platforms and
15+
# don't want rules_cc's auto-detected `local_config_cc` to compete with it.
16+
common --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
17+
common --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1
18+
1319
# Bazel 8 compatibility flags. We should find ways to avoid needing these
1420
common --legacy_external_runfiles
1521

@@ -85,6 +91,9 @@ build:windows --host_per_file_copt=external/protobuf\\+.*@/w
8591
# For build stamping
8692
common --enable_platform_specific_config
8793
common:linux --host_platform=//:local_linux_gnu
94+
# The LLVM toolchain doesn't ship libgcc_s; this stub satisfies Rust's
95+
# glibc stdlib link dependency when LLVM is the host C++ toolchain.
96+
common:linux --@llvm//config:experimental_stub_libgcc_s=True
8897
common:windows --host_platform=//:local_windows_gnullvm
8998
build:linux --workspace_status_command=scripts/build-info.sh
9099
build:macos --workspace_status_command=scripts/build-info.sh

BUILD.bazel

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,33 @@ platform(
129129
platform(
130130
name = "local_windows_gnullvm",
131131
constraint_values = [
132-
"@rules_rs//rs/platforms/constraints:windows_gnullvm",
132+
"@llvm//constraints/windows/abi:gnullvm",
133+
# Rust's `x86_64-pc-windows-gnullvm` and `aarch64-pc-windows-gnullvm`
134+
# toolchains both link against MSVCRT (see rustc's `windows_gnullvm`
135+
# spec), and rules_rs encodes that as a `windows/crt:msvcrt`
136+
# constraint on the toolchain. Without it here, no rust toolchain
137+
# matches the host platform on Windows and `bazel test //rust/...`
138+
# fails with "No matching toolchains found".
139+
"@llvm//constraints/windows/crt:msvcrt",
140+
],
141+
parents = ["@platforms//host"],
142+
)
143+
144+
# Native Windows host platform. Used as `--host_platform` on Windows
145+
# runners so native rust builds go through `x86_64-pc-windows-msvc` (which
146+
# the Windows runners have installed as part of Visual Studio) rather
147+
# than `x86_64-pc-windows-gnullvm`. The gnullvm path drags in LLVM's
148+
# bootstrap-process-wrapper Starlark transition, which puts
149+
# `process_wrapper` in a different exec config than proc-macro deps and
150+
# trips rustc with `can't find crate for time_macros / scroll_derive`
151+
# style errors. Cross-compile targets in //rust still transition to the
152+
# gnullvm rules_rs platforms so non-Windows hosts can produce PE binaries
153+
# without a Windows SDK.
154+
platform(
155+
name = "local_windows_msvc",
156+
constraint_values = [
157+
"@llvm//constraints/windows/abi:msvc",
158+
"@llvm//constraints/windows/crt:msvcrt",
133159
],
134160
parents = ["@platforms//host"],
135161
)

MODULE.bazel

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ bazel_dep(name = "bazel_features", version = "1.47.1")
1010
bazel_dep(name = "bazel_skylib", version = "1.9.0")
1111
bazel_dep(name = "buildifier_prebuilt", version = "8.5.1.2")
1212
bazel_dep(name = "contrib_rules_jvm", version = "0.33.0")
13-
bazel_dep(name = "llvm", version = "0.7.9")
13+
bazel_dep(name = "llvm", version = "0.8.0")
1414
bazel_dep(name = "package_metadata", version = "0.0.10")
1515
bazel_dep(name = "platforms", version = "1.1.0")
1616

@@ -26,6 +26,24 @@ single_version_override(
2626
version = "33.5",
2727
)
2828

29+
# We set `--@llvm//config:experimental_stub_libgcc_s=True` in .bazelrc so the
30+
# LLVM cc toolchain doesn't try to resolve a `-lgcc_s` it doesn't ship. In
31+
# v0.8.0 the `experimental_stub_libgcc_s` bool_flag isn't marked
32+
# `scope = "universal"`, so the flag value on the target config doesn't
33+
# propagate to exec config and tool builds (process_wrapper,
34+
# //rust:_selenium-manager-bin) still fail to link. The patch adds
35+
# `scope = "universal"` so the flag does propagate; can be dropped once
36+
# hermeticbuild/hermetic-llvm#544 lands.
37+
single_version_override(
38+
module_name = "llvm",
39+
patch_strip = 1,
40+
patches = [
41+
"//third_party/bazel:llvm_libgcc_s_universal_scope.patch",
42+
],
43+
version = "0.8.0",
44+
)
45+
46+
bazel_dep(name = "rules_android", version = "0.7.2")
2947
bazel_dep(name = "rules_closure", version = "0.16.0")
3048
bazel_dep(name = "rules_dotnet", version = "0.21.5")
3149

@@ -39,7 +57,17 @@ bazel_dep(name = "rules_pkg", version = "1.2.0")
3957
bazel_dep(name = "rules_python", version = "1.9.0")
4058
bazel_dep(name = "rules_proto", version = "7.1.0")
4159
bazel_dep(name = "rules_ruby", version = "0.26.0")
42-
bazel_dep(name = "rules_rs", version = "0.0.61")
60+
bazel_dep(name = "rules_rs", version = "0.0.76")
61+
62+
# Native C libraries pulled in by the `*-sys` crates that the Selenium
63+
# Manager Rust binary depends on. Pointing each `*-sys` crate at its
64+
# BCR-hosted cc_library (per rules_rs's `bazel mod tidy` suggestion) lets
65+
# the C source build with the LLVM cc toolchain — which is target-libc
66+
# aware, so the same crate compiles correctly for both the host and the
67+
# musl cross-compile targets.
68+
bazel_dep(name = "bzip2", version = "1.0.8.bcr.3")
69+
bazel_dep(name = "xz", version = "5.4.5.bcr.8")
70+
bazel_dep(name = "zstd", version = "1.5.7.bcr.1")
4371

4472
single_version_override(
4573
module_name = "rules_jvm_external",
@@ -388,8 +416,34 @@ use_repo(rust_toolchains, "default_rust_toolchains")
388416

389417
register_toolchains("@default_rust_toolchains//:all")
390418

391-
# TODO: Register only Windows exec for now, full @llvm migration can be done as followup.
392419
llvm_toolchains = use_extension("@llvm//extensions:toolchain.bzl", "toolchain")
420+
# Register the hermetic LLVM exec toolchain for every platform we build on so
421+
# that the C/C++ toolchain used for exec-config targets (process_wrapper, cargo
422+
# build scripts, etc.) is fully version-pinned and reproducible.
423+
#
424+
# macOS is now viable because the Rust TLS stack uses `ring` (pure Rust + light
425+
# C, no system-framework deps) instead of `aws-lc-rs` / `aws-lc-sys`, which
426+
# previously required CoreServices and compiler-rt from the Xcode sysroot.
427+
#
428+
# Windows uses the gnullvm ABI (PE binary, MSVCRT runtime) rather than MSVC
429+
# because the hermetic LLVM toolchain ships everything needed for gnullvm
430+
# targets without a separately-provisioned Visual Studio installation.
431+
llvm_toolchains.exec(
432+
arch = "x86_64",
433+
os = "linux",
434+
)
435+
llvm_toolchains.exec(
436+
arch = "aarch64",
437+
os = "linux",
438+
)
439+
llvm_toolchains.exec(
440+
arch = "x86_64",
441+
os = "macos",
442+
)
443+
llvm_toolchains.exec(
444+
arch = "aarch64",
445+
os = "macos",
446+
)
393447
llvm_toolchains.exec(
394448
arch = "x86_64",
395449
os = "windows",
@@ -409,25 +463,60 @@ crate.from_cargo(
409463
cargo_toml = "//rust:Cargo.toml",
410464
platform_triples = [
411465
"aarch64-apple-darwin",
466+
"aarch64-pc-windows-gnullvm",
412467
"aarch64-pc-windows-msvc",
413468
"aarch64-unknown-linux-gnu",
469+
"aarch64-unknown-linux-musl",
414470
"x86_64-apple-darwin",
471+
"x86_64-pc-windows-gnullvm",
415472
"x86_64-pc-windows-msvc",
416473
"x86_64-unknown-linux-gnu",
474+
"x86_64-unknown-linux-musl",
417475
],
418476
)
477+
478+
# Both crates ship `readme.md` but their source uses `include_str!("../readme.md")`.
479+
# Bazel's Rust sandbox only includes `.rs` source files — not auxiliary data files
480+
# like README — so the include_str! fails at compile time regardless of case. We
481+
# patch out the doc attribute entirely; no documentation needed in a Bazel build.
482+
crate.annotation(
483+
crate = "windows-link",
484+
patch_args = ["-p1"],
485+
patches = ["//third_party/bazel:windows_link_readme_case.patch"],
486+
)
487+
crate.annotation(
488+
crate = "windows-targets",
489+
patch_args = ["-p1"],
490+
patches = ["//third_party/bazel:windows_targets_readme_case.patch"],
491+
version = "0.53.4",
492+
)
493+
494+
# Point the `*-sys` crates at the BCR cc_library packages so their cargo
495+
# build scripts don't run. The `inject_repo` calls below make the
496+
# corresponding repos visible to the crate extension. (Auto-suggested by
497+
# `bazel mod tidy` against rules_rs's well-known annotations.)
419498
crate.annotation(
420499
crate = "bzip2-sys",
421-
gen_build_script = "on",
500+
gen_build_script = "off",
501+
deps = ["@bzip2//:bz2"],
422502
)
423503
crate.annotation(
424504
crate = "lzma-sys",
425-
gen_build_script = "on",
505+
gen_build_script = "off",
506+
deps = ["@xz//:lzma"],
426507
)
427508
crate.annotation(
428509
crate = "zstd-sys",
429-
gen_build_script = "on",
510+
gen_build_script = "off",
511+
deps = ["@zstd"],
430512
)
513+
514+
inject_repo(crate, "bzip2")
515+
516+
inject_repo(crate, "xz")
517+
518+
inject_repo(crate, "zstd")
519+
431520
use_repo(crate, "crates")
432521

433522
selenium_manager_artifacts = use_extension("//common:selenium_manager.bzl", "selenium_manager_artifacts")

common/BUILD.bazel

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ config_setting(
4141
constraint_values = ["@platforms//os:windows"],
4242
)
4343

44+
# Per-(OS, arch) host config_settings.
45+
[
46+
config_setting(
47+
name = "{}_{}".format(os, arch),
48+
constraint_values = [
49+
"@platforms//os:{}".format(os),
50+
"@platforms//cpu:{}".format(arch),
51+
],
52+
)
53+
for (os, arch) in [
54+
("linux", "aarch64"),
55+
("linux", "x86_64"),
56+
("macos", "aarch64"),
57+
("windows", "aarch64"),
58+
("windows", "x86_64"),
59+
]
60+
]
61+
4462
# Are we creating a stamped build?
4563
config_setting(
4664
name = "stamp",

common/manager/BUILD.bazel

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
load("//common:defs.bzl", "copy_file")
2+
13
package(
24
default_visibility = [
35
"//dotnet/src/webdriver:__pkg__",
@@ -9,26 +11,61 @@ package(
911
],
1012
)
1113

14+
# Tree of every cross-built Selenium Manager binary, laid out as
15+
# `<os>-<arch>/selenium-manager[.exe]`. Language packagers that can ship
16+
# directories (Ruby gem data, npm package files, Python wheel data) take
17+
# this directly; tools that only handle individual files (Java resources,
18+
# nuget pack) consume the per-arch single-file targets below.
1219
alias(
13-
name = "selenium-manager-linux",
14-
actual = select({
15-
"//common:use_pinned_browser": "@download_sm_linux//file",
16-
"//conditions:default": "//rust:selenium-manager-linux",
17-
}),
20+
name = "selenium-manager-bundle",
21+
actual = "//rust:selenium-manager-bundle",
1822
)
1923

20-
alias(
21-
name = "selenium-manager-macos",
22-
actual = select({
23-
"//common:use_pinned_browser": "@download_sm_macos//file",
24-
"//conditions:default": "//rust:selenium-manager-macos",
25-
}),
26-
)
24+
# Single-file targets per (os, arch) — same binaries the bundle contains,
25+
# but as individual files (each output at `<os>-<arch>/selenium-manager[.exe]`)
26+
# for tools whose resource attrs don't expand tree artifacts (Java
27+
# `resources`, nuget_pack `files`).
28+
#
29+
# On Windows hosts the cross-compile chain through rules_rs's LLVM
30+
# toolchain currently fails for proc-macro deps, so we fall back to the
31+
# native rust_binary for the host's architecture (via `//rust:_selenium-manager-bin`)
32+
# and stub the rest. Release jars/nupkgs are built on Linux, where every
33+
# entry is real.
34+
[
35+
copy_file(
36+
name = "selenium-manager-{}-{}".format(os, arch),
37+
src = select({
38+
"//common:windows_aarch64": (
39+
"//rust:_selenium-manager-bin" if (os, arch) == ("windows", "aarch64") else "//rust:_selenium-manager-stub"
40+
),
41+
"//common:windows_x86_64": (
42+
"//rust:_selenium-manager-bin" if (os, arch) == ("windows", "x86_64") else "//rust:_selenium-manager-stub"
43+
),
44+
"//conditions:default": "//rust:_bundle_member_{}_{}".format(os, arch),
45+
}),
46+
out = "{}-{}/selenium-manager{}".format(
47+
os,
48+
arch,
49+
".exe" if os == "windows" else "",
50+
),
51+
is_executable = True,
52+
)
53+
for (os, arch) in [
54+
("linux", "aarch64"),
55+
("linux", "x86_64"),
56+
("macos", "aarch64"),
57+
("windows", "aarch64"),
58+
("windows", "x86_64"),
59+
]
60+
]
2761

28-
alias(
29-
name = "selenium-manager-windows",
30-
actual = select({
31-
"//common:use_pinned_browser": "@download_sm_windows//file",
32-
"//conditions:default": "//rust:selenium-manager-windows",
33-
}),
62+
filegroup(
63+
name = "selenium-manager-files",
64+
srcs = [
65+
":selenium-manager-linux-aarch64",
66+
":selenium-manager-linux-x86_64",
67+
":selenium-manager-macos-aarch64",
68+
":selenium-manager-windows-aarch64",
69+
":selenium-manager-windows-x86_64",
70+
],
3471
)

common/manager/defs.bzl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
load("@aspect_bazel_lib//lib:copy_directory.bzl", "copy_directory")
2+
3+
def selenium_manager_bundle(name, out, **kwargs):
4+
"""Copies the cross-compiled Selenium Manager bundle tree into this package.
5+
6+
Args:
7+
name: rule name
8+
out: destination path within the package's output tree
9+
**kwargs: forwarded to copy_directory (e.g. visibility)
10+
"""
11+
copy_directory(
12+
name = name,
13+
src = "//common/manager:selenium-manager-bundle",
14+
out = out,
15+
**kwargs
16+
)

dotnet/private/nuget_pack.bzl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,22 @@ def nuget_pack_impl(ctx):
7777

7878
working_dir = ctx.label.name + "-working-dir"
7979

80-
# Copy files directly into the working directory layout (no intermediate zip)
80+
# Copy files directly into the working directory layout (no intermediate zip).
81+
# Tree artifacts (directories produced e.g. by copy_to_directory) are copied
82+
# recursively into the destination directory.
8183
copy_cmds = []
8284
for (file, rel_path) in layout.items():
8385
dest = working_dir + "/" + rel_path
84-
copy_cmds.append("mkdir -p \"$(dirname '{dest}')\" && cp '{src}' '{dest}'".format(
85-
dest = dest,
86-
src = file.path,
87-
))
86+
if file.is_directory:
87+
copy_cmds.append("mkdir -p '{dest}' && cp -R '{src}'/. '{dest}'".format(
88+
dest = dest,
89+
src = file.path,
90+
))
91+
else:
92+
copy_cmds.append("mkdir -p \"$(dirname '{dest}')\" && cp '{src}' '{dest}'".format(
93+
dest = dest,
94+
src = file.path,
95+
))
8896

8997
cmd_parts = [
9098
"rm -rf '%s'" % working_dir,

0 commit comments

Comments
 (0)