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.

47 changes: 46 additions & 1 deletion ios/BUILD
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
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,
)

swift_library(
name = "HelloAppLibrary",
srcs = ["HelloApp.swift"],
Expand Down Expand Up @@ -49,7 +65,36 @@ 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"],
)

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)
}
}
59 changes: 59 additions & 0 deletions tools/simulator_manager/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load("@rules_swift//swift:swift.bzl", "swift_binary", "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 plain swift_binary is equivalent here.
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",
],
)

swift_test(
name = "simulator_manager_test",
srcs = glob(["Tests/*.swift"]),
tags = ["no-linux-ci"],
deps = [":simulator_manager"],
)

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"],
)
176 changes: 176 additions & 0 deletions tools/simulator_manager/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions tools/simulator_manager/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// swift-tools-version:5.9

// Declares the external Swift packages that //tools/simulator_manager needs.
// rules_swift_package_manager resolves these into @swiftpkg_* repositories; the
// products are wired into the swift_binary in BUILD.bazel.

import PackageDescription

let package = Package(
name: "simulator_manager",
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.8.2"),
.package(url: "https://github.com/apple/swift-nio", from: "2.101.3"),
.package(url: "https://github.com/apple/swift-nio-extras", from: "1.34.3"),
.package(url: "https://github.com/JohnSundell/ShellOut", from: "2.3.0"),
]
)
Loading
Loading