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 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import class Foundation.ProcessInfo
import class Foundation.Thread
import class Foundation.UserDefaults
import class Foundation.NSNumber
/// A namespace for user defaults. Swift Build's domain is 'org.swift.swift-build'.
///
/// By convention, all values here should directly query the default for each access -- clients should cache this value if necessary.
///
/// Note:
/// We don't want to access UserDefaults directly, since it's global state.
/// That means it can change during the build, and we also found that accessing UserDefaults can be slow in hot code paths
/// since it takes a lock on the user defaults state.
/// Instead, take a look at ``UserPreferences``, which is designed to capture UserDefaults into an immutable object at the beginning of the build,
/// and then pass that object down to subobjects that need it during the build.
public enum UserDefaults: Sendable {
/// Binds the internal defaults to the specified `environment` for the duration of the synchronous `operation`.
/// - parameter clean: `true` to start with a clean environment, `false` to merge the input environment over the existing process environment.
/// - note: This is implemented via task-local values.
@_spi(Testing) public static func withEnvironment<R>(_ environment: [String: String], clean: Bool = false, operation: () throws -> R) rethrows -> R {
try $internalDefaults.withValue(.init(clean ? environment.p : internalDefaults.dictionary.addingContents(of: environment.p)), operation: operation)
}
/// Binds the internal defaults to the specified `environment` for the duration of the asynchronous `operation`.
/// - parameter clean: `true` to start with a clean environment, `false` to merge the input environment over the existing process environment.
/// - note: This is implemented via task-local values.
@_spi(Testing) public static func withEnvironment<R>(_ environment: [String: String], clean: Bool = false, operation: () async throws -> R) async rethrows -> R {
try await $internalDefaults.withValue(.init(clean ? environment.p : internalDefaults.dictionary.addingContents(of: environment.p)), operation: operation)
}
/// Internally overridden defaults. This returns the process environment as user defaults.
///
/// By convention the lookup methods for user defaults in this class look at the internal defaults returned here before the defaults in the swift-build domain, so environment variables take precedence over the normal defaults.
///
/// The global function `exportUserDefaultToEnvironment()` can be used to add a user default key-value pair for the current process to its environment. In Xcode, `SWBBuildServiceConnection.init()` does this in order to add selected Xcode-side user defaults to the environment it passes to Swift Build.
@TaskLocal fileprivate static var internalDefaults = Registry<String, PropertyListItem>(ProcessInfo.processInfo.environment.p)
/// We load user defaults from the `org.swift.swift-build` domain. By convention the lookup methods will override these with the process environment. See the `internalDefaults` property for more details.
nonisolated(unsafe) private static let defaults: Foundation.UserDefaults = {
let defaults = Foundation.UserDefaults(suiteName: "org.swift.swift-build")!
// Preserved for backwards compatibility
defaults.addSuite(named: "com.apple.dt.XCBuild")
return defaults
}()
/// Set a temporary string-value default.
public static func set(key: String, value: String?) {
internalDefaults[key] = value.map { .plString($0) }
}
/// Set a temporary bool-value default.
public static func set(key: String, value: Bool) {
internalDefaults[key] = .plBool(value)
}
/// Set a temporary string-array-value default.
public static func set(key: String, value: [String]) {
internalDefaults[key] = .plArray(value.map({ .plString($0) }))
}
/// Clear a temporary default.
public static func reset(key: String) -> PropertyListItem? {
return internalDefaults.removeValue(forKey: key)
}
/// Get a boolean default value.
public static func bool(forKey key: String) -> Bool {
return internalDefaults[key]?.looselyTypedBoolValue ?? UserDefaults.defaults.bool(forKey: key)
}
/// Get a string default value.
public static func string(forKey key: String) -> String? {
return internalDefaults[key]?.stringValue ?? UserDefaults.defaults.string(forKey: key)
}
/// Get an Int default value.
public static func int(forKey key: String) -> Int {
// Note: Can't use internalDefaults[key].intValue here because internalDefaults only registers .plString values
if let stringValue = internalDefaults[key]?.stringValue {
return Int(stringValue) ?? 0
}
return UserDefaults.defaults.integer(forKey: key)
}
/// Get a string array default value. If the value is a string, it will be automatically wrapped in an array.
public static func stringArray(forKey key: String) -> [String]? {
if let value = internalDefaults[key]?.stringValue {
return [value]
}
return internalDefaults[key]?.stringArrayValue ?? UserDefaults.defaults.stringArray(forKey: key)
}
/// Returns `true` if the given user default is defined.
public static func hasValue(forKey key: String) -> Bool {
return internalDefaults[key] != nil || UserDefaults.defaults.object(forKey: key) != nil
}
// MARK: Actual Build Defaults
public static var skipEarlyBuildOperationCancellation: Bool {
return bool(forKey: "SkipEarlyBuildOperationCancellation")
}
/// Whether removal of stale files is enabled (on by default).
public static var enableBuildSystemStaleFileRemoval: Bool {
return !hasValue(forKey: "EnableBuildSystemStaleFileRemoval") || bool(forKey: "EnableBuildSystemStaleFileRemoval")
}
/// Whether to skip reporting of build debugging is enabled.
public static var skipLogReporting: Bool {
return bool(forKey: "SkipLogReporting")
}
/// Whether the device-agnostic file system mode should be used. The default is `true`.
/// (See <rdar://problem/38916860> Building after a reboot does a full rebuild)
public static var ignoreFileSystemDeviceInodeChanges: Bool {
return hasValue(forKey: "IgnoreFileSystemDeviceInodeChanges") ? bool(forKey: "IgnoreFileSystemDeviceInodeChanges") : true
}
/// (See <rdar://problem/99632656> Make incremental builds resilient to content-preserving touch and git branch switch)
public static var fileSystemMode: FileSystemMode {
let defaultMode: FileSystemMode = .deviceAgnostic
if hasValue(forKey: "FileSystemMode") {
switch string(forKey: "FileSystemMode") {
case "checksum-only":
return .checksumOnly
case "full-stat":
return .fullStat
case "device-agnostic":
return .deviceAgnostic
default:
return defaultMode
}
}
if hasValue(forKey: "IgnoreFileSystemDeviceInodeChanges") {
if bool(forKey: "IgnoreFileSystemDeviceInodeChanges") {
return .deviceAgnostic
} else {
return .fullStat
}
}
return defaultMode
}
/// Additional directories to search for platforms.
public static var additionalPlatformSearchPaths: [Path] {
return stringArray(forKey: "DVTExtraPlatformFolders")?.compactMap({ $0.isEmpty ? nil : Path($0) }) ?? []
}
/// Whether serialization and writing-to-disk of a BuildDescription should be done synchronously. Default is `false`.
///
/// This default is intended for use only for comparative performance measurements.
public static var useSynchronousBuildDescriptionSerialization: Bool {
return bool(forKey: "UseSynchronousBuildDescriptionSerialization")
}
/// Whether the dependency cycle resolution should be attempted.
public static var attemptDependencyCycleResolution: Bool {
return bool(forKey: "AttemptDependencyCycleResolution")
}
/// Whether llbuild tracing points should be enabled.
public static var enableTracing: Bool {
return bool(forKey: "EnableTracing")
}
public static var enableDiagnosingDiamondProblemsWhenUsingPackages: Bool {
return hasValue(forKey: "EnableDiagnosingDiamondProblemsWhenUsingPackages") ? bool(forKey: "EnableDiagnosingDiamondProblemsWhenUsingPackages") : true
}
public static var enableIndexingPayloadSerialization: Bool {
return bool(forKey: "EnableIndexingPayloadSerialization")
}
public static var enablePluginManagerLogging: Bool {
return bool(forKey: "EnablePluginManagerLogging")
}
public static var disableSigningProvisioningErrors: Bool {
return bool(forKey: "DisableSigningProvisioningErrors")
}
public static var makeAggregateTargetsTransparentForSpecialization: Bool {
return hasValue(forKey: "MakeAggregateTargetsTransparentForSpecialization") ? bool(forKey: "MakeAggregateTargetsTransparentForSpecialization") : true
}
public static var enableSDKStatCaching: Bool {
return hasValue(forKey: "EnableSDKStatCaching") ? bool(forKey: "EnableSDKStatCaching") : true
}
public static var enableCASValidation: Bool {
return hasValue(forKey: "EnableCASValidation") ? bool(forKey: "EnableCASValidation") : true
}
public static var useTargetDependenciesForImpartedBuildSettings: Bool {
return bool(forKey: "UseTargetDependenciesForImpartedBuildSettings")
}
public static var enableFixFor23297285: Bool {
return !hasValue(forKey: "EnableFixFor23297285") || bool(forKey: "EnableFixFor23297285")
}
/// Provides a mechanism to control the concurrency behavior when calculating the dependency graph. This can be especially useful to disable when tracking down and testing ordering issues.
public static var disableConcurrentDependencyResolution: Bool {
return hasValue(forKey: "DisableConcurrentDependencyResolution") ? bool(forKey: "DisableConcurrentDependencyResolution") : false
}
public static var buildDescriptionInMemoryCacheSize: Int {
return hasValue(forKey: "BuildDescriptionInMemoryCacheSize") ? int(forKey: "BuildDescriptionInMemoryCacheSize") : 4
}
public static var buildDescriptionInMemoryCostLimit: Int {
return hasValue(forKey: "BuildDescriptionInMemoryCostLimit") ? int(forKey: "BuildDescriptionInMemoryCostLimit") : 50_000
}
public static var buildDescriptionOnDiskCacheSize: Int {
return hasValue(forKey: "BuildDescriptionOnDiskCacheSize") ? int(forKey: "BuildDescriptionOnDiskCacheSize") : 4
}
/// What the llbuild scheduler lane width should be, where 0 (the default)
/// means use the available hardware concurrency.
public static var schedulerLaneWidth: UInt32? {
if let laneWidthString = string(forKey: "SchedulerLaneWidth"), let laneWidth = UInt32(laneWidthString) {
return laneWidth
} else if let maxTasksString = string(forKey: "IDEBuildOperationMaxNumberOfConcurrentCompileTasks"), let maxTasks = UInt32(maxTasksString) {
return maxTasks
}
return nil
}
/// What llbuild scheduler algorithm should be used.
public static var schedulerAlgorithm: String? {
return string(forKey: "SchedulerAlgorithm")
}
/// The on-disk path to use for the compilation cache.
public static var compilationCachingCASPath: String? {
return string(forKey: "CompilationCachingCASPath")
}
/// See `CASOptions.parseSizeLimit()` for the format of the string.
public static var compilationCachingDiskSizeLimit: String? {
return string(forKey: "CompilationCachingDiskSizeLimit")
}
/// Provides the default level of QoS support within Swift Build for global queues that are not tied to specific build requests.
public static var undeterminedQoS: SWBQoS {
// With 'unspecified' the QoS of the caller is influencing the QoS to be used for the enqueued work item.
return qosFromKey("UndeterminedQoS", defaultValue: .unspecified)
}
/// Provides the default level of QoS support for requests that do not have parameterized QoS.
public static var defaultRequestQoS: SWBQoS {
return qosFromKey("DefaultRequestQoS", defaultValue: .default)
}
private static func qosFromKey(_ key: String, defaultValue: SWBQoS) -> SWBQoS {
guard let qos = string(forKey: key) else { return defaultValue }
switch qos {
case "background": return .background
case "utility": return .utility
case "userInitiated": return .userInitiated
case "userInteractive": return .userInteractive
case "unspecified": return .unspecified
default: return defaultValue
}
}
/// Provide a mechanism to skip the run destination override
public static var skipRunDestinationOverride: Bool {
return bool(forKey: "XCBUILD_SKIP_RUN_DESTINATION_OVERRIDE")
}
/// Provide a mechanism to warn instead of error when specialization fails to choose a platform
public static var platformSpecializationWarnOnly: Bool {
return bool(forKey: "XCBUILD_PLATFORM_SPECIALIZATION_WARN_ONLY")
}
/// Provide a mechanism to avoid the `-rpath /usr/lib/swift` addition for Swift Concurrency. This is a fallback mechanism and is only intended to be used as a safeguard if any errors come up.
public static var allowRuntimeSearchPathAdditionForSwiftConcurrency: Bool {
return hasValue(forKey: "AllowRuntimeSearchPathAdditionForSwiftConcurrency") ? bool(forKey: "AllowRuntimeSearchPathAdditionForSwiftConcurrency") : true
}
public static var stopAfterOpeningLibClang: Bool {
return hasValue(forKey: "StopAfterOpeningLibClang") ? bool(forKey: "StopAfterOpeningLibClang") : false
}
public static var previewsAllowInternalMacOSDebugDylib: Bool {
return hasValue(forKey: "PreviewsEnableInternalMacOSDebugDylib") ? bool(forKey: "PreviewsEnableInternalMacOSDebugDylib") : false
}
}
/// The level of activity text shortening
/// - warning: Note that since the enum cases represent increasing levels of shortening, their ordering is important. In other words, `.legacy` represents no shortening, while `.full` represents the most shortening. This means, for example, that the build task counts text will be shortened if the level is >= `.buildCountsOnly`
public enum ActivityTextShorteningLevel: Int, Codable, Comparable, RawRepresentable, Sendable {
/// Use Xcode 12 activity text
case legacy
/// Only shorten the progress message for build task counts
case buildCountsOnly
/// Shorten all messages with dynamic text (counts or target names)
case allDynamicText
/// Shorten all build system progress messages; move some behind `enableDebugActivityLogs`
case full
public static let `default`: ActivityTextShorteningLevel = .full
}
// MARK: -
// MARK: Exporting user defaults from Xcode to Swift Build.
/// List of user defaults we want Xcode to export from its domain down to Swift Build.
///
/// These defaults primarily fall into two sets:
///
/// - Swift Build-defined defaults which are useful to specify on the xcodebuild command line, or when launching Xcode e.g. for testing.
/// - Defaults which existed in the legacy build system but which are also supported by Swift Build.
///
/// These defaults will be exported via `exportUserDefaultToEnvironment()` - which see for its specific behaviors.
public let xcodeUserDefaultsToExportToSwiftBuild = [
// Defaults controlling special diagnostic and debugging.
"EnableDebugActivityLogs",
"EnableBuildDebugging",
"SkipLogReporting",
"ActivityTextShorteningLevel",
// Defaults useful for enabling or disabling specific features.
"UsePerConfigurationBuildLocations",
"AttemptDependencyCycleResolution",
"IgnoreFileSystemDeviceInodeChanges",
"FileSystemMode",
"DVTExtraPlatformFolders",
// Legacy build system defaults also supported by Swift Build.
"IDEBuildOperationMaxNumberOfConcurrentCompileTasks",
"EnableFixFor23297285",
]
/// Global function which exports the supplied user default key, if present in the standard UserDefaults, to the process environment if it has not already been set.
public func exportUserDefaultToEnvironment(_ key: String) {
if let _ = getEnvironmentVariable(EnvironmentKey(key)) {
// do not override existing environment variable
return
}
if let udval = Foundation.UserDefaults.standard.string(forKey: key) {
// export UserDefault value to the environment as key
try? POSIX.setenv(key, udval, 1)
}
}
fileprivate extension Registry {
convenience init(_ dictionary: [Key: Value]) {
self.init()
for (key, value) in dictionary {
self[key] = value
}
}
var dictionary: [Key: Value] {
var dict: [Key: Value] = [:]
forEach { (key, value) in
dict[key] = value
}
return dict
}
}
fileprivate extension Dictionary where Key == String, Value == String {
var p: [String: PropertyListItem] {
// Presently, values are always treated as strings. A client would need to convert it to any other type (such as an array) manually.
mapValues { .plString($0) }
}
}
extension Task where Failure == Never {
/// Runs `block` in a new thread and suspends until it finishes execution.
///
/// - note: This function should be used sparingly, such as for long-running operations that may block and therefore should not be run on the Swift Concurrency thread pool. Do not use this for operations for which there may be many concurrent invocations as it could lead to thread explosion. It is meant to be a bridge to pre-existing blocking code which can't easily be converted to use Swift concurrency features.
public static func detachNewThread(name: String? = nil, _ block: @Sendable @escaping () -> Success) async -> Success {
let env = UserDefaults.internalDefaults.dictionary
return await withCheckedContinuation { continuation in
Thread.detachNewThread {
Thread.current.name = name
return continuation.resume(returning: UserDefaults.$internalDefaults.withValue(.init(env), operation: block))
}
}
}
}
|