diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 5626e8a51..05e91febd 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -39,12 +39,16 @@ x_defaults: - "-//test:output_file_map_default" windows_common: &windows_common platform: windows - build_flags: - # Override 'sandboxed' strategy set in .bazelrc because it's not - # available on Windows - - "--strategy=SwiftCompile=" build_targets: - "//tools/..." + # Cross-platform Swift examples that exercise the Windows host toolchain: + # a `swift_binary` executable and a `linkshared` Windows DLL. + - "//examples/xplatform/hello_world" + - "//examples/xplatform/shared_library" + test_targets: + # Exercises XCTest discovery, the test runner, and `swift_test` execution + # on Windows. + - "//examples/xplatform/xctest" tasks: macos_latest: @@ -97,11 +101,31 @@ tasks: - "curl https://download.swift.org/swift-${SWIFT_VERSION}-release/ubuntu2204/swift-${SWIFT_VERSION}-RELEASE/swift-${SWIFT_VERSION}-RELEASE-ubuntu22.04.tar.gz | tar xvz --strip-components=1 -C $SWIFT_HOME" <<: *linux_common - # TODO: re-enable when Windows in Bazel CI is properly configured for Swift. - # windows_last_green: - # name: "Last Green Bazel" - # bazel: last_green - # <<: *windows_common + windows: + name: "Current LTS" + bazel: latest + # Expose the installed Swift toolchain and the MSVC/Windows SDK environment to + # the build. The swift.org installer (`batch_commands` below) records its + # Toolchains/Runtimes bin on the user's Path and sets SDKROOT, and a Visual + # Studio "developer prompt" (vcvars) would set INCLUDE/LIB for swiftc's clang + # to find the C headers (errno.h, ...) and for the linker — but none of that + # reaches the already-running CI process. Set it all here. BazelCI expands + # these with os.path.expandvars, so %VAR% resolves and earlier keys (listed + # first) are available to later ones. The VS/SDK versions are pinned to the + # Bazel CI Windows image; update them if `vcvars64.bat`'s output changes. + environment: + SWIFT_VERSION: "6.3.2" + VCToolsInstallDir: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\VC\\Tools\\MSVC\\14.39.33519" + WindowsSdkDir: "C:\\Program Files (x86)\\Windows Kits\\10" + WindowsSDKVersion: "10.0.26100.0" + Path: "%LOCALAPPDATA%\\Programs\\Swift\\Toolchains\\%SWIFT_VERSION%+Asserts\\usr\\bin;%LOCALAPPDATA%\\Programs\\Swift\\Runtimes\\%SWIFT_VERSION%\\usr\\bin;%LOCALAPPDATA%\\Programs\\Swift\\Tools\\%SWIFT_VERSION%;%VCToolsInstallDir%\\bin\\Hostx64\\x64;%WindowsSdkDir%\\bin\\%WindowsSDKVersion%\\x64;%PATH%" + SDKROOT: "%LOCALAPPDATA%\\Programs\\Swift\\Platforms\\%SWIFT_VERSION%\\Windows.platform\\Developer\\SDKs\\Windows.sdk" + INCLUDE: "%VCToolsInstallDir%\\include;%WindowsSdkDir%\\include\\%WindowsSDKVersion%\\ucrt;%WindowsSdkDir%\\include\\%WindowsSDKVersion%\\shared;%WindowsSdkDir%\\include\\%WindowsSDKVersion%\\um;%WindowsSdkDir%\\include\\%WindowsSDKVersion%\\winrt;%WindowsSdkDir%\\include\\%WindowsSDKVersion%\\cppwinrt" + LIB: "%VCToolsInstallDir%\\lib\\x64;%WindowsSdkDir%\\lib\\%WindowsSDKVersion%\\ucrt\\x64;%WindowsSdkDir%\\lib\\%WindowsSDKVersion%\\um\\x64" + batch_commands: + - "curl -sSL -o %TEMP%\\swift-installer.exe https://download.swift.org/swift-%SWIFT_VERSION%-release/windows10/swift-%SWIFT_VERSION%-RELEASE/swift-%SWIFT_VERSION%-RELEASE-windows10.exe" + - "%TEMP%\\swift-installer.exe -q" + <<: *windows_common doc_tests: name: "Doc tests" diff --git a/.bazelrc b/.bazelrc index 35388d8b9..bada4255d 100644 --- a/.bazelrc +++ b/.bazelrc @@ -35,6 +35,12 @@ common:linux --repo_env=CC=clang build:linux --cxxopt='-std=c++17' --host_cxxopt='-std=c++17' common:linux --//test:apple_build_tests=False +# Worker sandboxing copies the worker into a sandbox exec root and cleans it +# between invocations. On Windows a running/recently-run executable cannot be +# deleted, so that cleanup fails with "Permission denied". Run Swift workers +# unsandboxed on Windows. +build:windows --noworker_sandboxing + # This C2K warning causes zlib to fail to compile. # There is an open issue about it on the zlib repository here: # https://github.com/madler/zlib/issues/633 diff --git a/swift/internal/compiling.bzl b/swift/internal/compiling.bzl index 0db3d6b62..36fae1757 100644 --- a/swift/internal/compiling.bzl +++ b/swift/internal/compiling.bzl @@ -1667,7 +1667,9 @@ def _declare_per_source_output_file(actions, extension, target_name, src): The declared `File`. """ objs_dir = "{}_objs".format(target_name) - owner_rel_path = owner_relative_path(src) + + # Spaces in object file paths break response-file parsing on Windows + owner_rel_path = owner_relative_path(src).replace(" ", "_") basename = paths.basename(owner_rel_path) dirname = paths.join(objs_dir, paths.dirname(owner_rel_path)) diff --git a/swift/internal/swift_autoconfiguration.bzl b/swift/internal/swift_autoconfiguration.bzl index 35b183cb8..c5bc500e5 100644 --- a/swift/internal/swift_autoconfiguration.bzl +++ b/swift/internal/swift_autoconfiguration.bzl @@ -179,6 +179,22 @@ def _normalized_linux_cpu(cpu): return "x86_64" return cpu +def _normalized_windows_cpu(cpu): + """Normalizes a host CPU name to the value Swift uses on Windows. + + The returned value is used both as the toolchain's `arch` and as the + architecture component of the Swift SDK's library layout (for example + `usr/lib/swift/windows/x86_64`) and the target triple. + """ + cpu = cpu.lower() + if cpu in ("amd64", "x86_64", "x64"): + return "x86_64" + if cpu in ("arm64", "aarch64"): + return "aarch64" + if cpu in ("x86", "i686"): + return "i686" + return cpu + def _resolve_toolchain_root(repository_ctx, swiftc_path): """Returns the Swift toolchain root directory for `swiftc_path`. @@ -269,15 +285,28 @@ xcode_swift_toolchain( ]), ) +def _python_executable_works(repository_ctx, python_bin): + """Returns True if `python_bin` is a real, runnable Python 3 interpreter. + + On Windows, `python3.exe`/`python.exe` found on `PATH` are frequently the + Microsoft Store "App execution alias" stubs rather than real interpreters: + when run non-interactively they print a message pointing at the Store and + exit nonzero. Probe the candidate so those stubs are skipped in favor of a + working interpreter later on `PATH`. We also confirm it is Python 3, since + the caller needs the Python 3 `plistlib` API. + """ + result = repository_ctx.execute( + [python_bin, "-c", "import sys; print('ok' if sys.version_info[0] == 3 else 'no')"], + ) + return result.return_code == 0 and result.stdout.strip() == "ok" + def _get_python_bin(repository_ctx): if "PYTHON_BIN_PATH" in repository_ctx.os.environ: return repository_ctx.os.environ.get("PYTHON_BIN_PATH").strip() - out = repository_ctx.which("python3.exe") - if out: - return out - out = repository_ctx.which("python.exe") - if out: - return out + for name in ("python3.exe", "python3", "python.exe", "python"): + candidate = repository_ctx.which(name) + if candidate and _python_executable_works(repository_ctx, candidate): + return candidate return None def _create_windows_toolchain(*, repository_ctx): @@ -294,6 +323,7 @@ Swift toolchain. """ root = path_to_swiftc.dirname.dirname + arch = _normalized_windows_cpu(repository_ctx.os.arch) enabled_features = [ SWIFT_FEATURE_CODEVIEW_DEBUG_INFO, SWIFT_FEATURE_DECLARE_SWIFTSOURCEINFO, @@ -305,22 +335,34 @@ Swift toolchain. disabled_features = [] version_file, parsed_version = _write_swift_version(repository_ctx, path_to_swiftc) + + # Normalize SDKROOT to forward slashes with no trailing separator: the raw + # environment value typically ends in a backslash, which is both invalid at + # the end of a Python raw-string literal (used below) and produces doubled + # separators when joined. + sdkroot = repository_ctx.os.environ["SDKROOT"].replace("\\", "/").rstrip("/") + + info_plist = sdkroot + "/../../../Info.plist" + python_bin = _get_python_bin(repository_ctx) + if not python_bin: + fail("Could not find a working Python 3 interpreter on PATH; it is " + + "required to read the XCTest version from the Swift SDK's Info.plist.") xctest_version = repository_ctx.execute([ - _get_python_bin(repository_ctx), + python_bin, "-c", - "import os, plistlib; " + - "print(plistlib.loads(open(os.path.join(r'{}', '..', '..', '..', 'Info.plist'), 'rb').read(), fmt=plistlib.FMT_XML)['DefaultProperties']['XCTEST_VERSION'])".format(repository_ctx.os.environ["SDKROOT"]), + "import plistlib; " + + "print(plistlib.load(open(r'{}', 'rb'))['DefaultProperties']['XCTEST_VERSION'])".format(info_plist), ]) env = { "Path": repository_ctx.os.environ["Path"] if "Path" in repository_ctx.os.environ else repository_ctx.os.environ["PATH"], - "ProgramData": repository_ctx.os.environ["ProgramData"], + "ProgramData": repository_ctx.os.environ.get("ProgramData", "C:\\ProgramData"), } return """\ swift_toolchain( name = "windows-toolchain", - arch = "x86_64", + arch = "{arch}", features = [{features}], os = "windows", root = "{root}", @@ -332,11 +374,12 @@ swift_toolchain( xctest_version = "{xctest_version}", ) """.format( + arch = arch, features = ", ".join(['"{}"'.format(feature) for feature in enabled_features] + ['"-{}"'.format(feature) for feature in disabled_features]), root = root, env = env, parsed_version = parsed_version, - sdkroot = repository_ctx.os.environ["SDKROOT"].replace("\\", "/"), + sdkroot = sdkroot, xctest_version = xctest_version.stdout.rstrip(), version_file = version_file, ) diff --git a/swift/swift_test.bzl b/swift/swift_test.bzl index f6065bfa1..25cd3d8a9 100644 --- a/swift/swift_test.bzl +++ b/swift/swift_test.bzl @@ -94,6 +94,7 @@ def _generate_test_discovery_srcs( *, actions, deps, + env = {}, name, objc_test_discovery, owner_module_name, @@ -109,6 +110,9 @@ def _generate_test_discovery_srcs( Args: actions: The context's actions object. deps: The list of direct dependencies of the test target. + env: Environment variables to set when running the discovery tool. On + Windows this must include a `Path` that contains the Swift runtime + DLLs, otherwise the (Swift) discovery executable fails to launch. name: The name of the target being built, which will be used to derive the basename of the directory containing the generated files. objc_test_discovery: If `True`, the runner should use Objective-C-based @@ -197,6 +201,7 @@ def _generate_test_discovery_srcs( actions.run( arguments = [args], + env = env, executable = test_discoverer, exec_group = _DISCOVER_TESTS_EXEC_GROUP, inputs = inputs, @@ -411,6 +416,7 @@ def _swift_test_impl(ctx): discovery_srcs = _generate_test_discovery_srcs( actions = ctx.actions, deps = ctx.attr.deps, + env = toolchains.swift.test_configuration.env, name = ctx.label.name, objc_test_discovery = objc_test_discovery, owner_module_name = module_name, diff --git a/swift/toolchains/BUILD b/swift/toolchains/BUILD index 89d84641d..df9328c65 100644 --- a/swift/toolchains/BUILD +++ b/swift/toolchains/BUILD @@ -138,6 +138,21 @@ toolchain( visibility = ["//visibility:public"], ) +toolchain( + name = "windows-swift-toolchain-aarch64", + exec_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:aarch64", + ], + target_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:aarch64", + ], + toolchain = "@rules_swift_local_config//:windows-toolchain", + toolchain_type = "//toolchains:toolchain_type", + visibility = ["//visibility:public"], +) + # Consumed by Bazel integration tests. filegroup( name = "for_bazel_tests", diff --git a/swift/toolchains/swift_toolchain.bzl b/swift/toolchains/swift_toolchain.bzl index 02792563e..68460ba72 100644 --- a/swift/toolchains/swift_toolchain.bzl +++ b/swift/toolchains/swift_toolchain.bzl @@ -267,6 +267,7 @@ def _all_action_configs(os, arch, target_triple, sdkroot, xctest_version, additi actions = all_compile_action_names() + [ SWIFT_ACTION_DUMP_AST, SWIFT_ACTION_PRECOMPILE_C_MODULE, + SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], configurators = [ add_arg( @@ -294,6 +295,7 @@ def _all_action_configs(os, arch, target_triple, sdkroot, xctest_version, additi actions = all_compile_action_names() + [ SWIFT_ACTION_DUMP_AST, SWIFT_ACTION_PRECOMPILE_C_MODULE, + SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT, ], configurators = [ add_arg( @@ -364,6 +366,13 @@ def _swift_windows_linkopts_cc_info( "-LIBPATH:{}".format(platform_lib_dir), "-LIBPATH:{}".format(paths.join(sdkroot, "..", "..", "Library", "XCTest-{}".format(xctest_version), "usr", "lib", "swift", "windows", arch)), runtime_object_path, + # Swift marks references to symbols in other modules (for example the + # type metadata accessors a generated test runner references via + # `@testable import`) as `dllimport`. Bazel links everything statically, + # so those symbols resolve locally and `link.exe` emits LNK4217. The + # warning is benign for static linking; suppress it so it is not fatal + # under `/WX` (treat-warnings-as-errors). + "-IGNORE:4217", ] return CcInfo( @@ -480,6 +489,17 @@ def _entry_point_linkopts_provider(*, entry_point_name): linkopts = ["-Wl,--defsym,main={}".format(entry_point_name)], ) +def _windows_entry_point_linkopts_provider(*, entry_point_name): + """Returns linkopts to customize the entry point of a binary on Windows. + + MSVC `link.exe` does not understand the GNU `ld` `--defsym` alias used on + other platforms; `/ALTERNATENAME` is the equivalent, resolving the + CRT-referenced `main` symbol to the renamed Swift entry point. + """ + return struct( + linkopts = ["/ALTERNATENAME:main={}".format(entry_point_name)], + ) + def _parse_target_system_name(*, arch, os, target_system_name): """Returns the target system name set by the CC toolchain or attempts to create one based on the OS and arch.""" @@ -488,6 +508,10 @@ def _parse_target_system_name(*, arch, os, target_system_name): if os == "linux": return "%s-unknown-linux-gnu" % arch + elif os == "windows": + # The MSVC cc toolchain reports a `target_gnu_system_name` of "local", + # so synthesize the triple. + return "%s-unknown-windows-msvc" % arch else: return "%s-unknown-%s" % (arch, os) @@ -520,7 +544,7 @@ def _swift_toolchain_impl(ctx): target_triples.parse(ctx.var.get("CC_TARGET_TRIPLE") or target_system_name), ) - if "clang" not in cc_toolchain.compiler and "llvm" not in cc_toolchain.compiler: + if ctx.attr.os != "windows" and "clang" not in cc_toolchain.compiler and "llvm" not in cc_toolchain.compiler: fail("Swift requires the configured CC toolchain use clang. " + "Either use the locally installed LLVM by setting `CC=clang` in your environment " + "before invoking Bazel, or configure a Bazel LLVM CC toolchain. " + @@ -646,7 +670,7 @@ def _swift_toolchain_impl(ctx): bindir = "bin64" elif ctx.attr.arch == "i686": bindir = "bin32" - elif ctx.attr.arch == "arm64": + elif ctx.attr.arch in ("aarch64", "arm64"): bindir = "bin64a" else: fail("unsupported arch `{}`".format(ctx.attr.arch)) @@ -672,7 +696,9 @@ def _swift_toolchain_impl(ctx): cross_import_overlays = collect_cross_import_overlays(ctx.attr.cross_import_overlays), debug_outputs_provider = None, developer_dirs = [], - entry_point_linkopts_provider = _entry_point_linkopts_provider, + entry_point_linkopts_provider = ( + _windows_entry_point_linkopts_provider if ctx.attr.os == "windows" else _entry_point_linkopts_provider + ), feature_allowlists = [ target[SwiftFeatureAllowlistInfo] for target in ctx.attr.feature_allowlists diff --git a/tools/test_observer/BUILD b/tools/test_observer/BUILD index 241cd7a39..0c9ead01b 100644 --- a/tools/test_observer/BUILD +++ b/tools/test_observer/BUILD @@ -10,11 +10,11 @@ swift_library( "BazelXMLTestObserver.swift", "Concurrency.swift", "JSON.swift", - "LinuxXCTestRunner.swift", "Locked.swift", "ObjectiveCXCTestRunner.swift", "ShardingFilteringTestCollector.swift", "StringInterpolation+XMLEscaping.swift", + "SwiftCorelibsXCTestRunner.swift", "SwiftTestingRunner.swift", "XUnitTestRecorder.swift", ], diff --git a/tools/test_observer/Locked.swift b/tools/test_observer/Locked.swift index 06a897817..51b44b436 100644 --- a/tools/test_observer/Locked.swift +++ b/tools/test_observer/Locked.swift @@ -16,10 +16,53 @@ import Darwin #elseif canImport(Glibc) import Glibc +#elseif canImport(WinSDK) + import WinSDK #else #error("Unsupported platform") #endif +// The platform lock primitive stored alongside the value. POSIX platforms use a +// `pthread_mutex_t`; Windows uses a slim reader/writer lock (`SRWLOCK`), which +// needs no explicit destruction. +#if canImport(WinSDK) + private typealias LockPrimitive = SRWLOCK +#else + private typealias LockPrimitive = pthread_mutex_t +#endif + +private func _lockInitialize(_ lock: UnsafeMutablePointer) { + #if canImport(WinSDK) + InitializeSRWLock(lock) + #else + _ = pthread_mutex_init(lock, nil) + #endif +} + +private func _lockDestroy(_ lock: UnsafeMutablePointer) { + #if canImport(WinSDK) + // `SRWLOCK`s do not require destruction. + #else + _ = pthread_mutex_destroy(lock) + #endif +} + +private func _lockAcquire(_ lock: UnsafeMutablePointer) { + #if canImport(WinSDK) + AcquireSRWLockExclusive(lock) + #else + _ = pthread_mutex_lock(lock) + #endif +} + +private func _lockRelease(_ lock: UnsafeMutablePointer) { + #if canImport(WinSDK) + ReleaseSRWLockExclusive(lock) + #else + _ = pthread_mutex_unlock(lock) + #endif +} + /// A wrapper around a value that can be accessed safely from multiple threads in synchronized /// contexts. /// @@ -27,10 +70,10 @@ /// as XCTest's observer, both are called in synchronous contexts only, but we don't know what /// thread the calls are coming from. public struct Locked: Sendable where Value: Sendable { - private final class _Storage: ManagedBuffer { + private final class _Storage: ManagedBuffer { deinit { withUnsafeMutablePointerToElements { lock in - _ = pthread_mutex_destroy(lock) + _lockDestroy(lock) } } } @@ -38,9 +81,9 @@ public struct Locked: Sendable where Value: Sendable { // Swift 6 requires this to be declared as `nonisolated(unsafe)`, but older compilers emit a // warning claiming (incorrectly) that it's redundant. #if compiler(>=6) - private nonisolated(unsafe) var _storage: ManagedBuffer + private nonisolated(unsafe) var _storage: ManagedBuffer #else - private var _storage: ManagedBuffer + private var _storage: ManagedBuffer #endif /// The value behind the lock. @@ -52,7 +95,7 @@ public struct Locked: Sendable where Value: Sendable { public init(_ value: Value) { _storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in value }) _storage.withUnsafeMutablePointerToElements { lock in - _ = pthread_mutex_init(lock, nil) + _lockInitialize(lock) } } @@ -65,8 +108,8 @@ public struct Locked: Sendable where Value: Sendable { _ body: (inout Value) throws -> Result ) rethrows -> Result { try _storage.withUnsafeMutablePointers { rawValue, lock in - _ = pthread_mutex_lock(lock) - defer { _ = pthread_mutex_unlock(lock) } + _lockAcquire(lock) + defer { _lockRelease(lock) } return try body(&rawValue.pointee) } } diff --git a/tools/test_observer/LinuxXCTestRunner.swift b/tools/test_observer/SwiftCorelibsXCTestRunner.swift similarity index 89% rename from tools/test_observer/LinuxXCTestRunner.swift rename to tools/test_observer/SwiftCorelibsXCTestRunner.swift index 0ffd9a5b1..811b0ddbc 100644 --- a/tools/test_observer/LinuxXCTestRunner.swift +++ b/tools/test_observer/SwiftCorelibsXCTestRunner.swift @@ -12,18 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if os(Linux) +#if os(Linux) || os(Windows) import Foundation import XCTest - public typealias XCTestRunner = LinuxXCTestRunner + public typealias XCTestRunner = SwiftCorelibsXCTestRunner - /// A test runner for tests that use the XCTest framework on Linux. + /// A test runner for tests that use the XCTest framework on platforms that use + /// swift-corelibs-xctest (Linux and Windows). /// /// This test runner uses test case entries that were constructed by scanning the symbol graph - /// output of the compiler. + /// output of the compiler, since those platforms lack the Objective-C runtime used for test + /// discovery on Apple platforms. @MainActor - public enum LinuxXCTestRunner { + public enum SwiftCorelibsXCTestRunner { /// A wrapper around a single test from an `XCTestCaseEntry` used by the test collector. private struct Test: Testable { /// The type of the `XCTestCase` that contains the test. diff --git a/tools/test_observer/SwiftTestingRunner.swift b/tools/test_observer/SwiftTestingRunner.swift index 87b461b0a..4bf6a71b2 100644 --- a/tools/test_observer/SwiftTestingRunner.swift +++ b/tools/test_observer/SwiftTestingRunner.swift @@ -18,6 +18,8 @@ import Foundation import Darwin #elseif canImport(Glibc) import Glibc +#elseif canImport(WinSDK) + import WinSDK #else #error("Unsupported platform") #endif @@ -293,7 +295,7 @@ private struct SwiftTestingEntryPoint { /// Creates the entry point by looking it up by name in the current process, or fails if the /// entry point is not found. init?() { - guard let entryPointRaw = dlsym(rtldDefault, "swt_abiv0_getEntryPoint") else { + guard let entryPointRaw = _loadSwiftTestingSymbol("swt_abiv0_getEntryPoint") else { return nil } let abiv0_getEntryPoint = unsafeBitCast( @@ -344,18 +346,43 @@ private struct SwiftTestingEntryPoint { } } -// `RTLD_DEFAULT` is only defined on Linux when `_GNU_SOURCE` is defined. Just redefine it -// here for convenience. -#if compiler(>=5.10) - #if os(Linux) - private nonisolated(unsafe) let rtldDefault = UnsafeMutableRawPointer(bitPattern: 0) - #else - private nonisolated(unsafe) let rtldDefault = UnsafeMutableRawPointer(bitPattern: -2) - #endif +/// Looks up a symbol exported by the swift-testing framework in the current process, returning +/// `nil` if it is not present (i.e. swift-testing was not linked into the test binary). +#if canImport(WinSDK) + private func _loadSwiftTestingSymbol(_ name: String) -> UnsafeMutableRawPointer? { + // `GetProcAddress` resolves a symbol from a specific module, so check the modules that may + // export the swift-testing ABI: the test executable itself (when statically linked) and the + // `Testing.dll` shared library. + let modules: [HMODULE?] = [ + GetModuleHandleW(nil), + "Testing.dll".withCString(encodedAs: UTF16.self) { GetModuleHandleW($0) }, + ] + for module in modules { + guard let module else { continue } + if let symbol = name.withCString({ GetProcAddress(module, $0) }) { + return unsafeBitCast(symbol, to: UnsafeMutableRawPointer.self) + } + } + return nil + } #else - #if os(Linux) - private let rtldDefault = UnsafeMutableRawPointer(bitPattern: 0) + // `RTLD_DEFAULT` is only defined on Linux when `_GNU_SOURCE` is defined. Just redefine it + // here for convenience. + #if compiler(>=5.10) + #if os(Linux) + private nonisolated(unsafe) let rtldDefault = UnsafeMutableRawPointer(bitPattern: 0) + #else + private nonisolated(unsafe) let rtldDefault = UnsafeMutableRawPointer(bitPattern: -2) + #endif #else - private let rtldDefault = UnsafeMutableRawPointer(bitPattern: -2) + #if os(Linux) + private let rtldDefault = UnsafeMutableRawPointer(bitPattern: 0) + #else + private let rtldDefault = UnsafeMutableRawPointer(bitPattern: -2) + #endif #endif + + private func _loadSwiftTestingSymbol(_ name: String) -> UnsafeMutableRawPointer? { + return dlsym(rtldDefault, name) + } #endif diff --git a/tools/worker/work_processor.cc b/tools/worker/work_processor.cc index df6021c84..5e79f94a4 100644 --- a/tools/worker/work_processor.cc +++ b/tools/worker/work_processor.cc @@ -33,6 +33,30 @@ namespace { +#if defined(_WIN32) +// On Windows, `std::filesystem` honors the legacy MAX_PATH (260 character) limit +// unless a path uses the extended-length "\\?\" prefix. The incremental storage +// area (`bazel-out/.../_swift_incremental/...`) routinely produces paths longer +// than that, so normalize to an absolute, normalized, backslash-separated path +// with the prefix applied before performing filesystem operations. +std::filesystem::path LongPath(const std::filesystem::path& path) { + std::error_code ec; + std::filesystem::path absolute = std::filesystem::absolute(path, ec); + if (ec) { + return path; + } + std::wstring native = absolute.lexically_normal().make_preferred().wstring(); + if (native.compare(0, 4, L"\\\\?\\") != 0) { + native.insert(0, L"\\\\?\\"); + } + return std::filesystem::path(native); +} +#else +std::filesystem::path LongPath(const std::filesystem::path& path) { + return path; +} +#endif + bool copy_file(const std::filesystem::path& from, const std::filesystem::path& to, std::error_code& ec) noexcept { #if defined(__APPLE__) @@ -44,7 +68,7 @@ bool copy_file(const std::filesystem::path& from, ec = std::error_code(); return true; #else - return std::filesystem::copy_file(from, to, ec); + return std::filesystem::copy_file(LongPath(from), LongPath(to), ec); #endif } @@ -52,7 +76,7 @@ static bool TouchFile(const std::filesystem::path& path, std::ostringstream& output) { std::error_code ec; if (!path.parent_path().empty()) { - std::filesystem::create_directories(path.parent_path(), ec); + std::filesystem::create_directories(LongPath(path.parent_path()), ec); if (ec) { output << "swift_worker: Could not create directory " << path.parent_path() << " (" << ec.message() << ")\n"; @@ -60,7 +84,7 @@ static bool TouchFile(const std::filesystem::path& path, } } - std::ofstream stream(path); + std::ofstream stream(LongPath(path)); if (!stream) { output << "swift_worker: Could not create " << path << "\n"; return false; @@ -227,7 +251,7 @@ void WorkProcessor::ProcessWorkRequest( // requested. if (!emit_swift_source_info && expected_object_path.extension() == ".swiftsourceinfo") { - std::filesystem::remove(expected_object_path); + std::filesystem::remove(LongPath(expected_object_path)); } // Bazel creates the intermediate directories for the files declared at @@ -251,7 +275,7 @@ void WorkProcessor::ProcessWorkRequest( for (const auto& dir_path : dir_paths) { std::error_code ec; - std::filesystem::create_directories(dir_path, ec); + std::filesystem::create_directories(LongPath(dir_path), ec); if (ec) { stderr_stream << "swift_worker: Could not create directory " << dir_path << " (" << ec.message() << ")\n"; @@ -267,7 +291,7 @@ void WorkProcessor::ProcessWorkRequest( auto inputs = output_file_map.incremental_inputs(); bool all_inputs_exist = std::all_of( inputs.cbegin(), inputs.cend(), [](const auto& expected_object_pair) { - return std::filesystem::exists(expected_object_pair.second); + return std::filesystem::exists(LongPath(expected_object_pair.second)); }); if (all_inputs_exist) { @@ -286,12 +310,12 @@ void WorkProcessor::ProcessWorkRequest( } else { auto cleanup_outputs = output_file_map.incremental_cleanup_outputs(); for (const auto& cleanup_output : cleanup_outputs) { - if (!std::filesystem::exists(cleanup_output)) { + if (!std::filesystem::exists(LongPath(cleanup_output))) { continue; } std::error_code ec; - std::filesystem::remove(cleanup_output, ec); + std::filesystem::remove(LongPath(cleanup_output), ec); if (ec) { stderr_stream << "swift_worker: Could not remove " << cleanup_output << " (" << ec.message() << ")\n"; @@ -337,10 +361,10 @@ void WorkProcessor::ProcessWorkRequest( // next run. for (const auto& expected_object_pair : output_file_map.incremental_inputs()) { - if (std::filesystem::exists(expected_object_pair.first)) { - if (std::filesystem::exists(expected_object_pair.second)) { + if (std::filesystem::exists(LongPath(expected_object_pair.first))) { + if (std::filesystem::exists(LongPath(expected_object_pair.second))) { // CopyFile fails if the file already exists - std::filesystem::remove(expected_object_pair.second); + std::filesystem::remove(LongPath(expected_object_pair.second)); } std::error_code ec; copy_file(expected_object_pair.first, expected_object_pair.second, ec);