Skip to content

Commit 3d919b2

Browse files
[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).
1 parent ef8c989 commit 3d919b2

29 files changed

Lines changed: 759 additions & 272 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

@@ -89,6 +95,9 @@ build:windows --host_per_file_copt=external/protobuf\\+.*@/w
8995
# For build stamping
9096
common --enable_platform_specific_config
9197
common:linux --host_platform=//:local_linux_gnu
98+
# The LLVM toolchain doesn't ship libgcc_s; this stub satisfies Rust's
99+
# glibc stdlib link dependency when LLVM is the host C++ toolchain.
100+
common:linux --@llvm//config:experimental_stub_libgcc_s=True
92101
common:windows --host_platform=//:local_windows_gnullvm
93102

94103
# rules_rs 0.0.90 bundles a newer rules_rust whose cargo build-script runner

MODULE.bazel

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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.3",
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

@@ -41,6 +59,16 @@ bazel_dep(name = "rules_proto", version = "7.1.0")
4159
bazel_dep(name = "rules_ruby", version = "0.26.0")
4260
bazel_dep(name = "rules_rs", version = "0.0.90")
4361

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")
71+
4472
single_version_override(
4573
module_name = "rules_jvm_external",
4674
patch_strip = 1,
@@ -389,23 +417,42 @@ use_repo(rust_toolchains, "default_rust_toolchains")
389417

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

392-
# TODO: Register only Windows exec for now, full @llvm migration can be done as followup.
393420
llvm_toolchains = use_extension("@llvm//extensions:toolchain.bzl", "toolchain")
421+
422+
# Register the hermetic LLVM exec toolchain for every platform we build on so
423+
# that the C/C++ toolchain used for exec-config targets (process_wrapper, cargo
424+
# build scripts, etc.) is fully version-pinned and reproducible.
425+
#
426+
# macOS is now viable because the Rust TLS stack uses `ring` (pure Rust + light
427+
# C, no system-framework deps) instead of `aws-lc-rs` / `aws-lc-sys`, which
428+
# previously required CoreServices and compiler-rt from the Xcode sysroot.
429+
#
430+
# Windows uses the gnullvm ABI (PE binary, MSVCRT runtime) rather than MSVC
431+
# because the hermetic LLVM toolchain ships everything needed for gnullvm
432+
# targets without a separately-provisioned Visual Studio installation.
394433
llvm_toolchains.exec(
395434
arch = "x86_64",
396-
os = "windows",
435+
os = "linux",
397436
)
398437
llvm_toolchains.exec(
399438
arch = "aarch64",
400-
os = "windows",
439+
os = "linux",
440+
)
441+
llvm_toolchains.exec(
442+
arch = "x86_64",
443+
os = "macos",
401444
)
402445
llvm_toolchains.exec(
403446
arch = "aarch64",
404447
os = "macos",
405448
)
406449
llvm_toolchains.exec(
407450
arch = "x86_64",
408-
os = "macos",
451+
os = "windows",
452+
)
453+
llvm_toolchains.exec(
454+
arch = "aarch64",
455+
os = "windows",
409456
)
410457
use_repo(llvm_toolchains, "llvm_toolchains")
411458

@@ -418,25 +465,60 @@ crate.from_cargo(
418465
cargo_toml = "//rust:Cargo.toml",
419466
platform_triples = [
420467
"aarch64-apple-darwin",
468+
"aarch64-pc-windows-gnullvm",
421469
"aarch64-pc-windows-msvc",
422470
"aarch64-unknown-linux-gnu",
471+
"aarch64-unknown-linux-musl",
423472
"x86_64-apple-darwin",
473+
"x86_64-pc-windows-gnullvm",
424474
"x86_64-pc-windows-msvc",
425475
"x86_64-unknown-linux-gnu",
476+
"x86_64-unknown-linux-musl",
426477
],
427478
)
479+
480+
# Both crates ship `readme.md` but their source uses `include_str!("../readme.md")`.
481+
# Bazel's Rust sandbox only includes `.rs` source files — not auxiliary data files
482+
# like README — so the include_str! fails at compile time regardless of case. We
483+
# patch out the doc attribute entirely; no documentation needed in a Bazel build.
484+
crate.annotation(
485+
crate = "windows-link",
486+
patch_args = ["-p1"],
487+
patches = ["//third_party/bazel:windows_link_readme_case.patch"],
488+
)
489+
crate.annotation(
490+
crate = "windows-targets",
491+
patch_args = ["-p1"],
492+
patches = ["//third_party/bazel:windows_targets_readme_case.patch"],
493+
version = "0.53.4",
494+
)
495+
496+
# Point the `*-sys` crates at the BCR cc_library packages so their cargo
497+
# build scripts don't run. The `inject_repo` calls below make the
498+
# corresponding repos visible to the crate extension. (Auto-suggested by
499+
# `bazel mod tidy` against rules_rs's well-known annotations.)
428500
crate.annotation(
429501
crate = "bzip2-sys",
430-
gen_build_script = "on",
502+
gen_build_script = "off",
503+
deps = ["@bzip2//:bz2"],
431504
)
432505
crate.annotation(
433506
crate = "lzma-sys",
434-
gen_build_script = "on",
507+
gen_build_script = "off",
508+
deps = ["@xz//:lzma"],
435509
)
436510
crate.annotation(
437511
crate = "zstd-sys",
438-
gen_build_script = "on",
512+
gen_build_script = "off",
513+
deps = ["@zstd"],
439514
)
515+
516+
inject_repo(crate, "bzip2")
517+
518+
inject_repo(crate, "xz")
519+
520+
inject_repo(crate, "zstd")
521+
440522
use_repo(crate, "crates")
441523

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

common/BUILD.bazel

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,25 @@ 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+
("macos", "x86_64"),
58+
("windows", "aarch64"),
59+
("windows", "x86_64"),
60+
]
61+
]
62+
4463
# Are we creating a stamped build?
4564
config_setting(
4665
name = "stamp",

common/manager/BUILD.bazel

Lines changed: 57 additions & 17 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,25 +11,63 @@ 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+
("macos", "x86_64"),
58+
("windows", "aarch64"),
59+
("windows", "x86_64"),
60+
]
61+
]
2762

28-
# The Rust selenium-manager cannot be built from source on Windows (pending the
29-
# @llvm toolchain migration)
30-
alias(
31-
name = "selenium-manager-windows",
32-
actual = "@download_sm_windows//file",
63+
filegroup(
64+
name = "selenium-manager-files",
65+
srcs = [
66+
":selenium-manager-linux-aarch64",
67+
":selenium-manager-linux-x86_64",
68+
":selenium-manager-macos-aarch64",
69+
":selenium-manager-macos-x86_64",
70+
":selenium-manager-windows-aarch64",
71+
":selenium-manager-windows-x86_64",
72+
],
3373
)

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)