|
| 1 | +# Porting to new platforms |
| 2 | + |
| 3 | +<!-- |
| 4 | +This source file is part of the Swift.org open source project |
| 5 | +
|
| 6 | +Copyright (c) 2024 Apple Inc. and the Swift project authors |
| 7 | +Licensed under Apache License v2.0 with Runtime Library Exception |
| 8 | +
|
| 9 | +See https://swift.org/LICENSE.txt for license information |
| 10 | +See https://swift.org/CONTRIBUTORS.txt for Swift project authors |
| 11 | +--> |
| 12 | + |
| 13 | +When the Swift toolchain is ported to a new platform, it is necessary to port |
| 14 | +Swift Testing as well. This document contains various information, trivia, and |
| 15 | +deep wisdoms about porting Swift Testing. |
| 16 | + |
| 17 | +> [!NOTE] |
| 18 | +> This document uses Classic Mac OS ("Classic") as an example target platform. |
| 19 | +> In this hypothetical scenario, we assume that the Swift compiler identifies |
| 20 | +> Classic with `os(Classic)` and that the C++ compiler identifies it with |
| 21 | +> `defined(macintosh)`. Other platforms would be identified differently. |
| 22 | +
|
| 23 | +## Getting started |
| 24 | + |
| 25 | +Before you start the porting process, make sure you are very familiar with Swift |
| 26 | +and C++ as well as the C standard library and platform SDK for your target |
| 27 | +platform. |
| 28 | + |
| 29 | +Your first task when porting Swift Testing is ensuring that it builds. |
| 30 | + |
| 31 | +We've made an effort to ensure that as much of our code as possible is |
| 32 | +platform-agnostic. When building the toolchain for a new platform, you will |
| 33 | +hopefully find that Swift Testing builds out-of-the-box with few, if any, |
| 34 | +errors. |
| 35 | + |
| 36 | +> [!NOTE] |
| 37 | +> Swift Testing relies on the Swift [standard library](https://github.com/swiftlang/swift), |
| 38 | +> Swift macros (including the [swift-syntax](https://github.com/swiftlang/swift-syntax) package), |
| 39 | +> and [Foundation](https://github.com/apple/swift-foundation). These components |
| 40 | +> must build and (minimally) function before you will be able to successfully |
| 41 | +> build Swift Testing regardless of which platform you are porting to. |
| 42 | +
|
| 43 | +### Swift or C++? |
| 44 | + |
| 45 | +Generally, prefer to implement changes in Swift rather than C++ where possible. |
| 46 | +Swift Testing is a Swift package and our goal is to keep as much of it written |
| 47 | +in Swift as we can. Generally speaking, you should not need to write much code |
| 48 | +using C++. |
| 49 | + |
| 50 | +## Resolving "platform-specific implementation missing" warnings |
| 51 | + |
| 52 | +The package will _not_ build without warnings which you (or we) will need |
| 53 | +to resolve. These warnings take the form: |
| 54 | + |
| 55 | +> ⚠️ WARNING: Platform-specific implementation missing: ... |
| 56 | +
|
| 57 | +These warnings may be emitted by our internal C++ module (`_TestingInternals`) |
| 58 | +or by our library module (`Testing`). Both indicate areas of our code that needs |
| 59 | +platform-specific attention. |
| 60 | + |
| 61 | +Most platform dependencies can be resolved through the use of platform-specific |
| 62 | +API. For example, Swift Testing uses the C11 standard [`timespec`](https://en.cppreference.com/w/c/chrono/timespec) |
| 63 | +type to accurately track the durations of test runs. If you are porting Swift |
| 64 | +Testing to Classic, you will run into trouble getting the UTC time needed by |
| 65 | +`Test.Clock`, but you could use the platform-specific [`GetDateTime()`](https://developer.apple.com/library/archive/documentation/mac/pdf/Operating_System_Utilities/DT_And_M_Utilities.pdf) |
| 66 | +function to get the current system time. |
| 67 | + |
| 68 | +### Including system headers |
| 69 | + |
| 70 | +Before we can call `GetDateTime()` from Swift, we need the Swift compiler to be |
| 71 | +able to see it. Swift Testing includes an internal clang module, |
| 72 | +`_TestingInternals`, that includes any system-provided C headers that we use as |
| 73 | +well as a small amount of C++ glue code (for code that cannot currently be |
| 74 | +implemented directly in Swift.) `GetDateTime()` is declared in `DateTimeUtils.h` |
| 75 | +on Classic, so we would add that header to `Includes.h` in the internal target: |
| 76 | + |
| 77 | +```diff |
| 78 | +--- a/Sources/_TestingInternals/include/Includes.h |
| 79 | ++++ b/Sources/_TestingInternals/include/Includes.h |
| 80 | + |
| 81 | ++#if defined(macintosh) |
| 82 | ++#include <DateTimeUtils.h> |
| 83 | ++#endif |
| 84 | +``` |
| 85 | + |
| 86 | +We intentionally don't import platform-specific C standard library modules |
| 87 | +(`Darwin`, `Glibc`, `WinSDK`, etc.) in Swift because they often include overlay |
| 88 | +code written in Swift and adding those modules as dependencies would make it |
| 89 | +more difficult to test that Swift code using Swift Testing. |
| 90 | + |
| 91 | +### Changes in Swift |
| 92 | + |
| 93 | +Once the header is included, we can call `GetDateTime()` from `Clock.swift`: |
| 94 | + |
| 95 | +```diff |
| 96 | +--- a/Sources/Testing/Events/Clock.swift |
| 97 | ++++ b/Sources/Testing/Events/Clock.swift |
| 98 | + |
| 99 | + fileprivate(set) var wall: TimeValue = { |
| 100 | + #if !SWT_NO_TIMESPEC |
| 101 | + // ... |
| 102 | ++#elseif os(Classic) |
| 103 | ++ var seconds = CUnsignedLong(0) |
| 104 | ++ GetDateTime(&seconds) |
| 105 | ++ seconds -= 2_082_844_800 // seconds between epochs |
| 106 | ++ return TimeValue((seconds: Int64(seconds), attoseconds: 0)) |
| 107 | + #else |
| 108 | + #warning("Platform-specific implementation missing: UTC time unavailable (no timespec)") |
| 109 | + #endif |
| 110 | + } |
| 111 | +``` |
| 112 | + |
| 113 | +## Runtime test discovery |
| 114 | + |
| 115 | +When porting to a new platform, you may need to provide a new implementation for |
| 116 | +`enumerateTypeMetadataSections()` in `Discovery.cpp`. Test discovery is |
| 117 | +dependent on Swift metadata discovery which is an inherently platform-specific |
| 118 | +operation. |
| 119 | + |
| 120 | +_Most_ platforms will be able to reuse the implementation used by Linux and |
| 121 | +Windows that calls an internal Swift runtime function to enumerate available |
| 122 | +metadata. If you are porting Swift Testing to Classic, this function won't be |
| 123 | +available, so you'll need to write a custom implementation instead. Assuming |
| 124 | +that the Swift compiler emits section information into the resource fork on |
| 125 | +Classic, you could use the [Resource Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf) |
| 126 | +to load that information: |
| 127 | + |
| 128 | +```diff |
| 129 | +--- a/Sources/_TestingInternals/Discovery.cpp |
| 130 | ++++ b/Sources/_TestingInternals/Discovery.cpp |
| 131 | + |
| 132 | + // ... |
| 133 | ++#elif defined(macintosh) |
| 134 | ++template <typename SectionEnumerator> |
| 135 | ++static void enumerateTypeMetadataSections(const SectionEnumerator& body) { |
| 136 | ++ ResFileRefNum refNum; |
| 137 | ++ if (noErr == GetTopResourceFile(&refNum)) { |
| 138 | ++ ResFileRefNum oldRefNum = refNum; |
| 139 | ++ do { |
| 140 | ++ UseResFile(refNum); |
| 141 | ++ Handle handle = Get1NamedResource('swft', "\p__swift5_types"); |
| 142 | ++ if (handle && *handle) { |
| 143 | ++ size_t size = GetHandleSize(handle); |
| 144 | ++ body(*handle, size); |
| 145 | ++ } |
| 146 | ++ } while (noErr == GetNextResourceFile(refNum, &refNum)); |
| 147 | ++ UseResFile(oldRefNum); |
| 148 | ++ } |
| 149 | ++} |
| 150 | + #else |
| 151 | + #warning Platform-specific implementation missing: Runtime test discovery unavailable |
| 152 | + template <typename SectionEnumerator> |
| 153 | + static void enumerateTypeMetadataSections(const SectionEnumerator& body) {} |
| 154 | + #endif |
| 155 | +``` |
| 156 | + |
| 157 | +## C++ stub implementations |
| 158 | + |
| 159 | +Some symbols defined in C and C++ headers, especially "complex" macros, cannot |
| 160 | +be represented in Swift. The `_TestingInternals` module includes a header file, |
| 161 | +`Stubs.h`, where you can define thin wrappers around these symbols that are |
| 162 | +visible to Swift. For example, to use timers on Classic, you'll need to call |
| 163 | +`NewTimerUPP()` to define the timer's callback, but that symbol is sometimes |
| 164 | +declared as a macro and cannot be called from Swift. You can add a stub function |
| 165 | +to `Stubs.h`: |
| 166 | + |
| 167 | +```diff |
| 168 | +--- a/Sources/_TestingInternals/include/Stubs.h |
| 169 | ++++ b/Sources/_TestingInternals/include/Stubs.h |
| 170 | + |
| 171 | ++#if defined(macintosh) |
| 172 | ++static TimerUPP swt_NewTimerUPP(TimerProcPtr userRoutine) { |
| 173 | ++ return NewTimerUPP(userRoutine); |
| 174 | ++} |
| 175 | ++#endif |
| 176 | +``` |
| 177 | + |
| 178 | +Stub functions should generally be `static` to allow for inlining and when |
| 179 | +possible should be named to match the symbols they wrap. |
| 180 | + |
| 181 | +## Unavailable features |
| 182 | + |
| 183 | +You may find that some feature of C++, Swift, or Swift Testing cannot be ported |
| 184 | +to your target platform. For example, Swift Testing's `FileHandle` type includes |
| 185 | +an `isTTY` property to determine if a file handle refers to a pseudoterminal, |
| 186 | +but Classic did not implement pseudoterminals at the file system layer, so |
| 187 | +`isTTY` cannot be meaningfully implemented. |
| 188 | + |
| 189 | +For most situations like this one, you can guard the affected code with a |
| 190 | +platform conditional and provide a stub implementation: |
| 191 | + |
| 192 | +```diff |
| 193 | +--- a/Sources/Testing/Support/FileHandle.swift |
| 194 | ++++ b/Sources/Testing/Support/FileHandle.swift |
| 195 | + |
| 196 | + var isTTY: Bool { |
| 197 | + #if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI) |
| 198 | + // ... |
| 199 | ++#elseif os(Classic) |
| 200 | ++ return false |
| 201 | + #else |
| 202 | + #warning("Platform-specific implementation missing: cannot tell if a file is a TTY") |
| 203 | + return false |
| 204 | + #endif |
| 205 | + } |
| 206 | +``` |
| 207 | + |
| 208 | +If another function in Swift Testing asks if a file is a TTY, it will then |
| 209 | +always get a result of `false` (which is always the correct result on Classic.) |
| 210 | +No further changes are needed in this case. |
| 211 | + |
| 212 | +If your target platform is missing some feature that is used pervasively |
| 213 | +throughout Swift Testing, this approach may be insufficient. Please reach out to |
| 214 | +us in the Swift forums for advice. |
| 215 | + |
| 216 | +## Adding new dependencies |
| 217 | + |
| 218 | +Avoid adding new Swift package or toolchain library dependencies. Swift Testing |
| 219 | +needs to support running tests for all Swift targets except, for the moment, the |
| 220 | +Swift standard library itself. Adding a dependency on another Swift component |
| 221 | +means that that component may be unable to link to Swift Testing. If you find |
| 222 | +yourself needing to link to a Swift component, please reach out to us in the |
| 223 | +Swift forums for advice. |
| 224 | + |
| 225 | +> [!WARNING] |
| 226 | +> Swift Testing has some dependencies on Foundation, specifically to support our |
| 227 | +> JSON event stream. Do not add new uses of Foundation without talking to us |
| 228 | +> first. If you _do_ add any new uses of Foundation (including any related |
| 229 | +> modules such as CoreFoundation or FoundationEssentials), they _must_ be |
| 230 | +> imported using the `private` keyword. |
| 231 | +
|
| 232 | +It is acceptable to add dependencies on C or C++ modules that are included by |
| 233 | +default in the new target platform. For example, Classic always includes the |
| 234 | +[Memory Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/Memory/Memory_Preface.pdf), |
| 235 | +so there is no problem using it. On the other hand, [WorldScript](https://developer.apple.com/library/archive/documentation/mac/pdf/Text.pdf) |
| 236 | +is an optional component, so the Classic port of Swift Testing must be able to |
| 237 | +function when it is not installed. |
| 238 | + |
| 239 | +If you need Swift Testing to link to additional libraries at build time, be sure |
| 240 | +to update both the [package manifest](https://github.com/swiftlang/swift-testing/blob/main/Package.swift) |
| 241 | +and the library target's [CMake script](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/CMakeLists.txt) |
| 242 | +to include the necessary linker flags. |
| 243 | + |
| 244 | +## Adding CI jobs for the new platform |
| 245 | + |
| 246 | +The Swift project maintains a set of CI jobs that target various platforms. To |
| 247 | +add CI jobs for Swift Testing or the Swift toolchain, please contain the CI |
| 248 | +maintainers on the Swift forums. |
| 249 | + |
| 250 | +If you wish to host your own CI jobs, let us know: we'd be happy to run them as |
| 251 | +part of Swift Testing's regular development cycle. |
0 commit comments