File: Environment.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (237 lines) | stat: -rw-r--r-- 8,212 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
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 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
//

private import _TestingInternals

/// A type describing the environment of the current process.
///
/// This type can be used to access the current process' environment variables.
///
/// This type is not part of the public interface of the testing library.
enum Environment {
#if SWT_NO_ENVIRONMENT_VARIABLES
  /// Storage for the simulated environment.
  ///
  /// The mechanism by which this dictionary is initially populated depends on
  /// platform-specific implementation details. Callers should not read from
  /// this dictionary directly; use ``variable(named:)`` or ``flag(named:)``
  /// instead.
  static let simulatedEnvironment = Locked<[String: String]>()
#endif

  /// Split a string containing an environment variable's name and value into
  /// two strings.
  ///
  /// - Parameters:
  ///   - row: The environment variable, of the form `"KEY=VALUE"`.
  ///
  /// - Returns: The name and value of the environment variable, or `nil` if it
  ///   could not be parsed.
  private static func _splitEnvironmentVariable(_ row: String) -> (key: String, value: String)? {
    row.firstIndex(of: "=").map { equalsIndex in
      let key = String(row[..<equalsIndex])
      let value = String(row[equalsIndex...].dropFirst())
      return (key, value)
    }
  }

#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
  /// Get all environment variables from a POSIX environment block.
  ///
  /// - Parameters:
  ///   - environ: The environment block, i.e. the global `environ` variable.
  ///
  /// - Returns: A dictionary of environment variables.
  private static func _get(fromEnviron environ: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>) -> [String: String] {
    var result = [String: String]()

    for i in 0... {
      guard let rowp = environ[i] else {
        break
      }

      if let row = String(validatingCString: rowp),
         let (key, value) = _splitEnvironmentVariable(row) {
        result[key] = value
      }
    }

    return result
  }
#endif

#if SWT_TARGET_OS_APPLE && !SWT_NO_ENVIRONMENT_VARIABLES && !SWT_NO_DYNAMIC_LINKING
  /// A non-POSIX/non-portable function that locks for access to `environ`.
  ///
  /// If the `environ_lock_np()` function is not available on the current
  /// system, the value of this property is `nil`.
  private static let _environ_lock_np = {
    symbol(named: "environ_lock_np").map {
      unsafeBitCast($0, to: (@convention(c) () -> Void).self)
    }
  }()

  /// A non-POSIX/non-portable function that unlocks after access to `environ`.
  ///
  /// If the `environ_unlock_np()` function is not available on the current
  /// system, the value of this property is `nil`.
  private static let _environ_unlock_np = {
    symbol(named: "environ_unlock_np").map {
      unsafeBitCast($0, to: (@convention(c) () -> Void).self)
    }
  }()
#endif

  /// Get all environment variables in the current process.
  ///
  /// - Returns: A copy of the current process' environment dictionary.
  static func get() -> [String: String] {
#if SWT_NO_ENVIRONMENT_VARIABLES
    simulatedEnvironment.rawValue
#elseif SWT_TARGET_OS_APPLE
#if !SWT_NO_DYNAMIC_LINKING
    _environ_lock_np?()
    defer {
      _environ_unlock_np?()
    }
#endif
    return _get(fromEnviron: _NSGetEnviron()!.pointee!)
#elseif os(Linux)
    _get(fromEnviron: swt_environ())
#elseif os(WASI)
    _get(fromEnviron: __wasilibc_get_environ())
#elseif os(Windows)
    guard let environ = GetEnvironmentStringsW() else {
      return [:]
    }
    defer {
      FreeEnvironmentStringsW(environ)
    }

    var result = [String: String]()
    var rowp = environ
    while rowp.pointee != 0 {
      defer {
        rowp += wcslen(rowp) + 1
      }
      if let row = String.decodeCString(rowp, as: UTF16.self)?.result,
         let (key, value) = _splitEnvironmentVariable(row) {
        result[key] = value
      }
    }
    return result
#else
#warning("Platform-specific implementation missing: environment variables unavailable")
    return [:]
#endif
  }

  /// Get the environment variable with the specified name.
  ///
  /// - Parameters:
  ///   - name: The name of the environment variable.
  ///
  /// - Returns: The value of the specified environment variable, or `nil` if it
  ///   is not set for the current process.
  static func variable(named name: String) -> String? {
#if SWT_NO_ENVIRONMENT_VARIABLES
    simulatedEnvironment.rawValue[name]
#elseif SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
    // Acquire the `environ` lock if possible, then look for the right variable
    // in the block. This ensures we still hold the lock when we convert the
    // found C string to a Swift string, which we can't do with getenv(). If the
    // lock is unavailable, then this implementation is equivalent to Darwin's
    // getenv() implementation.
    _environ_lock_np?()
    defer {
      _environ_unlock_np?()
    }
    let environ = _NSGetEnviron()!.pointee!

    return name.withCString { name in
      for i in 0... {
        guard let rowp = environ[i] else {
          break
        }

        if let equals = strchr(rowp, CInt(UInt8(ascii: "="))) {
          let keyLength = UnsafeRawPointer(equals) - UnsafeRawPointer(rowp)
          if 0 == strncmp(rowp, name, keyLength) {
            return String(validatingCString: equals + 1)
          }
        }
      }
      return nil
    }
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)
    getenv(name).flatMap { String(validatingCString: $0) }
#elseif os(Windows)
    name.withCString(encodedAs: UTF16.self) { name in
      func getVariable(maxCount: Int) -> String? {
        withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: maxCount) { buffer in
          SetLastError(DWORD(ERROR_SUCCESS))
          let count = GetEnvironmentVariableW(name, buffer.baseAddress!, DWORD(buffer.count))
          if count == 0 {
            switch GetLastError() {
            case DWORD(ERROR_SUCCESS):
              // Empty String
              return ""
            case DWORD(ERROR_ENVVAR_NOT_FOUND):
              // The environment variable wasn't set.
              return nil
            case let errorCode:
              let error = Win32Error(rawValue: errorCode)
              fatalError("unexpected error when getting environment variable '\(name)': \(error) (\(errorCode))")
            }
          } else if count > buffer.count {
            // Try again with the larger count.
            return getVariable(maxCount: Int(count))
          }
          return String.decodeCString(buffer.baseAddress!, as: UTF16.self)?.result
        }
      }
      return getVariable(maxCount: 256)
    }
#else
#warning("Platform-specific implementation missing: environment variables unavailable")
    return nil
#endif
  }

  /// Get the boolean value of the environment variable with the specified name.
  ///
  /// - Parameters:
  ///   - name: The name of the environment variable.
  ///
  /// - Returns: The value of the specified environment variable, interpreted as
  ///   a boolean value, or `nil` if it is not set for the current process.
  ///
  /// If a value is set for the specified environment variable, it is parsed as
  /// a boolean value:
  ///
  /// - String values that can be parsed as instances of `Int64` or `UInt64` are
  ///   interpreted as `false` if they are equal to `0`, and `true` otherwise;
  /// - String values beginning with the letters `"t"`, `"T"`, `"y"`, or `"Y"`
  ///   are interpreted as `true`; and
  /// - All other non-`nil` string values are interpreted as `false`.
  static func flag(named name: String) -> Bool? {
    variable(named: name).map {
      if let signedValue = Int64($0) {
        return signedValue != 0
      }
      if let unsignedValue = UInt64($0) {
        return unsignedValue != 0
      }

      let first = $0.first
      return first == "t" || first == "T" || first == "y" || first == "Y"
    }
  }
}