Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ build:clang --host_cxxopt="-fvisibility=hidden"

# iOS setup
build:ios --ios_multi_cpus=sim_arm64
build:ios --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 16 Pro"
# The simulator device is not pinned here: which device profiles exist depends on
# the installed Xcode's iOS runtime, so any value checked in goes stale. Set it in
# .bazelrc.user, e.g.
#
# build:ios --@rules_apple//apple/build_settings:ios_simulator_device="iPhone 17"
#
# `xcrun simctl list devicetypes` lists what is available locally.

# Platform-specific options for each supported platform.
build:remote_linux_x64 --extra_execution_platforms=//platform/linux_x64
Expand Down
23 changes: 23 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,29 @@ use_repo(maven, "maven")
bazel_dep(name = "rules_kotlin", version = "2.3.20")
bazel_dep(name = "rules_perl", version = "1.1.1")
bazel_dep(name = "rules_swift", version = "4.0.0-rc2")

# Swift Package Manager deps, used by //tools/simulator_manager.
bazel_dep(name = "rules_swift_package_manager", version = "1.22.0")

swift_deps = use_extension(
"@rules_swift_package_manager//:extensions.bzl",
"swift_deps",
)
swift_deps.from_package(
declare_swift_deps_info = True,
resolved = "//tools/simulator_manager:Package.resolved",
swift = "//tools/simulator_manager:Package.swift",
)
use_repo(
swift_deps,
"swift_deps_info",
"swift_package",
"swiftpkg_shellout",
"swiftpkg_swift_argument_parser",
"swiftpkg_swift_nio",
"swiftpkg_swift_nio_extras",
)

bazel_dep(name = "rules_nodejs", version = "6.7.4")
bazel_dep(name = "aspect_rules_ts", version = "3.8.10")
bazel_dep(name = "aspect_bazel_lib", version = "2.22.5")
Expand Down
900 changes: 766 additions & 134 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

70 changes: 69 additions & 1 deletion ios/BUILD
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@rules_apple//apple:ios.bzl", "ios_application", "ios_ui_test", "ios_unit_test")
load(
"@rules_apple//apple/testing/default_runner:ios_xctestrun_runner.bzl",
"ios_xctestrun_runner",
)
load("@rules_swift//swift:swift.bzl", "swift_library")

# Leases each test its own simulator from //tools/simulator_manager, so
# concurrent actions on one worker never share a device.
ios_xctestrun_runner(
name = "ios_leased_simulator_runner",
clean_up_simulator_action = "//tools/simulator_manager:release_simulator",
create_simulator_action = "//tools/simulator_manager:lease_simulator",
random = False,
# reuse_simulator doubles as the exclusivity toggle for the lease: opting out
# of reuse means no other test shares this device.
reuse_simulator = False,
)

# Non-exclusive leases: concurrent tests may share one simulator. Sharing is
# opportunistic, not bounded — a request only joins a device that is already
# active, so tests dispatched simultaneously can still each get their own.
ios_xctestrun_runner(
name = "ios_shared_simulator_runner",
clean_up_simulator_action = "//tools/simulator_manager:release_simulator",
create_simulator_action = "//tools/simulator_manager:lease_simulator",
random = False,
reuse_simulator = True,
)

swift_library(
name = "HelloAppLibrary",
srcs = ["HelloApp.swift"],
Expand Down Expand Up @@ -49,7 +76,48 @@ swift_library(
ios_ui_test(
name = "HelloAppUITest",
minimum_os_version = "26.0",
runner = "@rules_apple//apple/testing/default_runner:ios_xctestrun_ordered_runner",
runner = ":ios_leased_simulator_runner",
test_host = ":HelloApp",
deps = [":HelloAppUITestLib"],
exec_properties = {
"sandboxAllowed": "False",
},
)

swift_library(
name = "HelloConcurrentTestLib",
testonly = True,
srcs = ["HelloConcurrentTest.swift"],
module_name = "HelloConcurrentTest",
)

# Exercises concurrent leasing: run with --runs_per_test to get several
# simultaneous leases from one target. Unlike HelloAppUITest this has no test
# host, so it takes rules_apple's `simctl spawn` path rather than `xcodebuild`.
#
# bazel test --config=ios --runs_per_test=3 //ios:HelloConcurrentTest
#
# Each run's log reports its simulator UDID; no two concurrent runs should match.
# Manual because it is sensitive to how much memory the host has — see the
# simulator_manager README.
ios_unit_test(
name = "HelloConcurrentTest",
minimum_os_version = "26.0",
runner = ":ios_leased_simulator_runner",
tags = ["manual"],
timeout = "long",
deps = [":HelloConcurrentTestLib"],
)

# Same source, but leasing non-exclusively: several concurrent runs should
# collapse onto fewer devices. Measured on a 16 GiB worker, 8 runs shared 2
# simulators.
ios_unit_test(
name = "HelloSharedSimulatorTest",
minimum_os_version = "26.0",
runner = ":ios_shared_simulator_runner",
tags = ["manual"],
timeout = "long",
deps = [":HelloConcurrentTestLib"],
)

17 changes: 17 additions & 0 deletions ios/HelloConcurrentTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import XCTest

/// Checks that concurrent tests each lease their own simulator.
///
/// Unlike HelloAppConcurrentUITest this has no test host, so rules_apple runs it
/// via `simctl spawn` rather than `xcodebuild`.
///
/// Several targets share this source; each logs its UDID, and no two should
/// match. The sleep holds the lease long enough for the runs to actually overlap.
class HelloConcurrentTest: XCTestCase {
func testHoldsItsOwnSimulator() {
let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "unknown"
print("CONCURRENT_UNIT_TEST_SIMULATOR_UDID=\(udid)")
XCTAssertEqual(1 + 1, 2)
Thread.sleep(forTimeInterval: 20)
}
}
75 changes: 75 additions & 0 deletions tools/simulator_manager/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load("@rules_swift//swift:swift.bzl", "swift_binary", "swift_library", "swift_test")

# A daemon that hands out leases on iOS simulators. Ported from
# https://github.com/bazelbuild/rules_apple/pull/2767, which builds it with an
# internal `macos_swift_tool` macro; a swift_library plus swift_binary is
# equivalent, and lets the tests link the library.
swift_binary(
name = "simulator_manager",
srcs = glob(["Sources/*.swift"]),
tags = ["no-linux-ci"],
deps = [
"@swiftpkg_shellout//:ShellOut",
"@swiftpkg_swift_argument_parser//:ArgumentParser",
"@swiftpkg_swift_nio//:NIO",
"@swiftpkg_swift_nio//:NIOCore",
"@swiftpkg_swift_nio//:NIOHTTP1",
"@swiftpkg_swift_nio//:NIOPosix",
"@swiftpkg_swift_nio_extras//:NIOExtras",
],
)

# Compiles the daemon's sources directly rather than depending on the binary
# above: linking a swift_binary into a swift_test duplicates `_main`. Main.swift
# is excluded for the same reason.
swift_test(
name = "simulator_manager_test",
srcs = glob(["Tests/*.swift"]) + glob(
["Sources/*.swift"],
exclude = ["Sources/Main.swift"],
),
module_name = "simulator_manager",
tags = ["no-linux-ci"],
deps = [
"@swiftpkg_shellout//:ShellOut",
"@swiftpkg_swift_argument_parser//:ArgumentParser",
"@swiftpkg_swift_nio//:NIO",
"@swiftpkg_swift_nio//:NIOCore",
"@swiftpkg_swift_nio//:NIOHTTP1",
"@swiftpkg_swift_nio//:NIOPosix",
"@swiftpkg_swift_nio_extras//:NIOExtras",
],
)

sh_binary(
name = "start",
srcs = ["start.sh"],
data = [
":prepare_simulator",
":simulator_manager",
],
visibility = ["//visibility:public"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)

sh_binary(
name = "prepare_simulator",
srcs = ["prepare_simulator.sh"],
)

# rules_apple's create/clean_up_simulator_action hooks. Using these instead of
# patching the runner template (as PR 2767 does) keeps rules_apple unmodified.
sh_binary(
name = "lease_simulator",
srcs = ["lease_simulator.sh"],
data = [":start"],
visibility = ["//visibility:public"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)

sh_binary(
name = "release_simulator",
srcs = ["release_simulator.sh"],
visibility = ["//visibility:public"],
)
Loading
Loading