File: Porting.md

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (313 lines) | stat: -rw-r--r-- 13,190 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# Porting to new platforms

<!--
This source file is part of the Swift.org open source project

Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
-->

When the Swift toolchain is ported to a new platform, it is necessary to port
Swift Testing as well. This document contains various information, trivia, and
deep wisdoms about porting Swift Testing.

> [!NOTE]
> This document uses Classic Mac OS ("Classic") as an example target platform.
> In this hypothetical scenario, we assume that the Swift compiler identifies
> Classic with `os(Classic)` and that the C++ compiler identifies it with
> `defined(macintosh)`. Other platforms would be identified differently.

## Getting started

Before you start the porting process, make sure you are very familiar with Swift
and C++ as well as the C standard library and platform SDK for your target
platform.

Your first task when porting Swift Testing is ensuring that it builds.

We've made an effort to ensure that as much of our code as possible is
platform-agnostic. When building the toolchain for a new platform, you will
hopefully find that Swift Testing builds out-of-the-box with few, if any,
errors.

> [!NOTE]
> Swift Testing relies on the Swift [standard library](https://github.com/swiftlang/swift),
> Swift macros (including the [swift-syntax](https://github.com/swiftlang/swift-syntax) package),
> and [Foundation](https://github.com/apple/swift-foundation). These components
> must build and (minimally) function before you will be able to successfully
> build Swift Testing regardless of which platform you are porting to.

### Swift or C++?

Generally, prefer to implement changes in Swift rather than C++ where possible.
Swift Testing is a Swift package and our goal is to keep as much of it written
in Swift as we can. Generally speaking, you should not need to write much code
using C++.

## Resolving "platform-specific implementation missing" warnings

The package will _not_ build without warnings which you (or we) will need
to resolve. These warnings take the form:

> ⚠️ WARNING: Platform-specific implementation missing: ...

These warnings may be emitted by our internal C++ module (`_TestingInternals`)
or by our library module (`Testing`). Both indicate areas of our code that need
platform-specific attention.

> [!NOTE]
> Rarely, you may encounter errors of a similar form:
> 
> > 🛑 ERROR: Platform-specific misconfiguration: ...
> 
> These errors are produced when the configuration you're trying to build has
> conflicting requirements (for example, attempting to enable support for pipes
> without also enabling support for file I/O.) You should be able to resolve
> these issues by updating Package.swift and/or CompilerSettings.cmake.

Most platform dependencies can be resolved through the use of platform-specific
API. For example, Swift Testing uses the C11 standard [`timespec`](https://en.cppreference.com/w/c/chrono/timespec)
type to accurately track the durations of test runs. If you are porting Swift
Testing to Classic, you will run into trouble getting the UTC time needed by
`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)
function to get the current system time.

### Including system headers

Before we can call `GetDateTime()` from Swift, we need the Swift compiler to be
able to see it. Swift Testing includes an internal clang module,
`_TestingInternals`, that includes any system-provided C headers that we use as
well as a small amount of C++ glue code (for code that cannot currently be
implemented directly in Swift.) `GetDateTime()` is declared in `DateTimeUtils.h`
on Classic, so we would add that header to `Includes.h` in the internal target:

```diff
--- a/Sources/_TestingInternals/include/Includes.h
+++ b/Sources/_TestingInternals/include/Includes.h

+#if defined(macintosh)
+#include <DateTimeUtils.h>
+#endif
```

We intentionally don't import platform-specific C standard library modules
(`Darwin`, `Glibc`, `WinSDK`, etc.) in Swift because they often include overlay
code written in Swift and adding those modules as dependencies would make it
more difficult to test that Swift code using Swift Testing. 

### Changes in Swift

Once the header is included, we can call `GetDateTime()` from `Clock.swift`:

```diff
--- a/Sources/Testing/Events/Clock.swift
+++ b/Sources/Testing/Events/Clock.swift

 fileprivate(set) var wall: TimeValue = {
 #if !SWT_NO_TIMESPEC
   // ...
+#elseif os(Classic)
+  var seconds = CUnsignedLong(0)
+  GetDateTime(&seconds)
+  seconds -= 2_082_844_800 // seconds between epochs
+  return TimeValue((seconds: Int64(seconds), attoseconds: 0))
 #else
 #warning("Platform-specific implementation missing: UTC time unavailable (no timespec)")
 #endif
 }
```

## Runtime test discovery

When porting to a new platform, you may need to provide a new implementation for
`enumerateTypeMetadataSections()` in `Discovery.cpp`. Test discovery is
dependent on Swift metadata discovery which is an inherently platform-specific
operation.

_Most_ platforms will be able to reuse the implementation used by Linux and
Windows that calls an internal Swift runtime function to enumerate available
metadata. If you are porting Swift Testing to Classic, this function won't be
available, so you'll need to write a custom implementation instead. Assuming
that the Swift compiler emits section information into the resource fork on
Classic, you could use the [Resource Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/MoreMacintoshToolbox.pdf)
to load that information:

```diff
--- a/Sources/_TestingInternals/Discovery.cpp
+++ b/Sources/_TestingInternals/Discovery.cpp

 // ...
+#elif defined(macintosh)
+template <typename SectionEnumerator>
+static void enumerateTypeMetadataSections(const SectionEnumerator& body) {
+  ResFileRefNum refNum;
+  if (noErr == GetTopResourceFile(&refNum)) {
+    ResFileRefNum oldRefNum = refNum;
+    do {
+      UseResFile(refNum);
+      Handle handle = Get1NamedResource('swft', "\p__swift5_types");
+      if (handle && *handle) {
+        auto imageAddress = reinterpret_cast<const void *>(static_cast<uintptr_t>(refNum));
+        SWTSectionBounds sb = { imageAddress, *handle, GetHandleSize(handle) };
+        bool stop = false;
+        body(sb, &stop);
+        if (stop) {
+          break;
+        }
+      }
+    } while (noErr == GetNextResourceFile(refNum, &refNum));
+    UseResFile(oldRefNum);
+  }
+}
 #else
 #warning Platform-specific implementation missing: Runtime test discovery unavailable (dynamic)
 template <typename SectionEnumerator>
 static void enumerateTypeMetadataSections(const SectionEnumerator& body) {}
 #endif
```

## Runtime test discovery with static linkage

If your platform does not support dynamic linking and loading, you will need to
use static linkage instead. Define the `"SWT_NO_DYNAMIC_LINKING"` compiler
conditional for your platform in both Package.swift and CompilerSettings.cmake,
then define the `sectionBegin` and `sectionEnd` symbols in Discovery.cpp:

```diff
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
 // ...
+#elif defined(macintosh)
+extern "C" const char sectionBegin __asm__("...");
+extern "C" const char sectionEnd __asm__("...");
 #else
 #warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
 static const char sectionBegin = 0;
 static const char& sectionEnd = sectionBegin;
 #endif
```

These symbols must have unique addresses corresponding to the first byte of the
test content section and the first byte _after_ the test content section,
respectively. Their linker-level names will be platform-dependent: refer to the
linker documentation for your platform to determine what names to place in the
`__asm__` attribute applied to each.

If you can't use `__asm__` on your platform, you can declare these symbols as
C++ references to linker-defined symbols:

```diff
diff --git a/Sources/_TestingInternals/Discovery.cpp b/Sources/_TestingInternals/Discovery.cpp
 // ...
+#elif defined(macintosh)
+extern "C" const char __linker_defined_begin_symbol;
+extern "C" const char __linker_defined_end_symbol;
+static const auto& sectionBegin = __linker_defined_begin_symbol;
+static const auto& sectionEnd = __linker_defined_end_symbol;
 #else
 #warning Platform-specific implementation missing: Runtime test discovery unavailable (static)
 static const char sectionBegin = 0;
 static const char& sectionEnd = sectionBegin;
 #endif
```

The names of `__linker_defined_begin_symbol` and `__linker_defined_end_symbol`
in this example are, as with the shorter implementation, platform-dependent.

## C++ stub implementations

Some symbols defined in C and C++ headers, especially "complex" macros, cannot
be represented in Swift. The `_TestingInternals` module includes a header file,
`Stubs.h`, where you can define thin wrappers around these symbols that are
visible to Swift. For example, to use timers on Classic, you'll need to call
`NewTimerUPP()` to define the timer's callback, but that symbol is sometimes
declared as a macro and cannot be called from Swift. You can add a stub function
to `Stubs.h`:

```diff
--- a/Sources/_TestingInternals/include/Stubs.h
+++ b/Sources/_TestingInternals/include/Stubs.h

+#if defined(macintosh)
+static TimerUPP swt_NewTimerUPP(TimerProcPtr userRoutine) {
+  return NewTimerUPP(userRoutine);
+}
+#endif
```

Stub functions should generally be `static` to allow for inlining and when
possible should be named to match the symbols they wrap.

## Unavailable features

You may find that some feature of C++, Swift, or Swift Testing cannot be ported
to your target platform. For example, Swift Testing's `FileHandle` type includes
an `isTTY` property to determine if a file handle refers to a pseudoterminal,
but Classic did not implement pseudoterminals at the file system layer, so
`isTTY` cannot be meaningfully implemented.

For most situations like this one, you can guard the affected code with a
platform conditional and provide a stub implementation:

```diff
--- a/Sources/Testing/Support/FileHandle.swift
+++ b/Sources/Testing/Support/FileHandle.swift

 var isTTY: Bool {
 #if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(Android) || os(WASI)
   // ...
+#elseif os(Classic)
+  return false
 #else
 #warning("Platform-specific implementation missing: cannot tell if a file is a TTY")
   return false
 #endif
 }
```

If another function in Swift Testing asks if a file is a TTY, it will then
always get a result of `false` (which is always the correct result on Classic.)
No further changes are needed in this case.

If your target platform is missing some feature that is used pervasively
throughout Swift Testing, this approach may be insufficient. Please reach out to
us in the Swift forums for advice.

## Adding new dependencies

Avoid adding new Swift package or toolchain library dependencies. Swift Testing
needs to support running tests for all Swift targets except, for the moment, the
Swift standard library itself. Adding a dependency on another Swift component
means that that component may be unable to link to Swift Testing. If you find
yourself needing to link to a Swift component, please reach out to us in the
Swift forums for advice.

> [!WARNING]
> Swift Testing has some dependencies on Foundation, specifically to support our
> JSON event stream. Do not add new uses of Foundation without talking to us
> first. If you _do_ add any new uses of Foundation (including any related
> modules such as CoreFoundation or FoundationEssentials), they _must_ be
> imported using the `private` keyword.

It is acceptable to add dependencies on C or C++ modules that are included by
default in the new target platform. For example, Classic always includes the
[Memory Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/Memory/Memory_Preface.pdf),
so there is no problem using it. On the other hand, [WorldScript](https://developer.apple.com/library/archive/documentation/mac/pdf/Text.pdf)
is an optional component, so the Classic port of Swift Testing must be able to
function when it is not installed.

If you need Swift Testing to link to additional libraries at build time, be sure
to update both the [package manifest](https://github.com/swiftlang/swift-testing/blob/main/Package.swift)
and the library target's [CMake script](https://github.com/swiftlang/swift-testing/blob/main/Sources/Testing/CMakeLists.txt)
to include the necessary linker flags.

## Adding CI jobs for the new platform

The Swift project maintains a set of CI jobs that target various platforms. To
add CI jobs for Swift Testing or the Swift toolchain, please contain the CI
maintainers on the Swift forums.

If you wish to host your own CI jobs, let us know: we'd be happy to run them as
part of Swift Testing's regular development cycle.