Platform.swift 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. extension CommandLine {
  12. /// Accesses the command line arguments in a concurrency-safe way.
  13. ///
  14. /// Workaround for https://github.com/apple/swift/issues/66213
  15. static let _staticArguments: [String] = Self.arguments
  16. }
  17. #if canImport(Glibc)
  18. @preconcurrency import Glibc
  19. #elseif canImport(Musl)
  20. @preconcurrency import Musl
  21. #elseif canImport(Darwin)
  22. import Darwin
  23. #elseif canImport(CRT)
  24. @preconcurrency import CRT
  25. #elseif canImport(WASILibc)
  26. @preconcurrency import WASILibc
  27. #elseif canImport(Android)
  28. @preconcurrency import Android
  29. #endif
  30. enum Platform {}
  31. // MARK: Environment
  32. extension Platform {
  33. enum Environment {
  34. struct Key {
  35. static let shell = Self(rawValue: "SHELL")
  36. static let columns = Self(rawValue: "COLUMNS")
  37. static let lines = Self(rawValue: "LINES")
  38. /// The name of the environment variable whose value is the name of the shell
  39. /// for which completions are being requested from a custom completion
  40. /// handler.
  41. ///
  42. /// The environment variable is set in generated completion scripts.
  43. static let shellName = Self(rawValue: "SAP_SHELL")
  44. /// The name of the environment variable whose value is the version of the
  45. /// shell for which completions are being requested from a custom completion
  46. /// handler.
  47. ///
  48. /// The environment variable is set in generated completion scripts.
  49. static let shellVersion = Self(rawValue: "SAP_SHELL_VERSION")
  50. var rawValue: String
  51. }
  52. @_disfavoredOverload
  53. static subscript(_ key: Key) -> String? {
  54. get {
  55. #if !os(Windows) && !os(WASI)
  56. guard let cString = getenv(key.rawValue) else { return nil }
  57. return String(cString: cString)
  58. #else
  59. return nil
  60. #endif
  61. }
  62. set {
  63. #if !os(Windows) && !os(WASI)
  64. if let newValue = newValue {
  65. setenv(key.rawValue, newValue, 1)
  66. } else {
  67. unsetenv(key.rawValue)
  68. }
  69. #endif
  70. }
  71. }
  72. static subscript<Value>(_ key: Key, as _: Value.Type) -> Value?
  73. where Value: LosslessStringConvertible
  74. {
  75. get {
  76. guard let stringValue = self[key] else { return nil }
  77. return Value(stringValue)
  78. }
  79. set {
  80. if let newValue = newValue {
  81. self[key] = newValue.description
  82. } else {
  83. self[key] = nil
  84. }
  85. }
  86. }
  87. static subscript<Value>(_ key: Key, as _: Value.Type) -> Value?
  88. where Value: RawRepresentable, Value.RawValue == String
  89. {
  90. get {
  91. guard let stringValue = self[key] else { return nil }
  92. return Value(rawValue: stringValue)
  93. }
  94. set {
  95. if let newValue = newValue {
  96. self[key] = newValue.rawValue
  97. } else {
  98. self[key] = nil
  99. }
  100. }
  101. }
  102. }
  103. }
  104. // MARK: Shell
  105. extension Platform {
  106. /// The name of the user's preferred shell, if detectable from the
  107. /// environment.
  108. static var shellName: String? {
  109. #if os(Windows)
  110. return nil
  111. #else
  112. // FIXME: This retrieves the user's preferred shell, not necessarily the one currently in use.
  113. guard let shellVar = Environment[.shell] else { return nil }
  114. let shellParts = shellVar.split(separator: "/")
  115. return shellParts.last.map(String.init)
  116. #endif
  117. }
  118. }
  119. // MARK: Exit codes
  120. #if os(Windows)
  121. import func WinSDK.GetStdHandle
  122. import func WinSDK.GetConsoleScreenBufferInfo
  123. import let WinSDK.ERROR_BAD_ARGUMENTS
  124. import let WinSDK.STD_OUTPUT_HANDLE
  125. import struct WinSDK.CONSOLE_SCREEN_BUFFER_INFO
  126. #endif
  127. extension Platform {
  128. /// The code for successful exit.
  129. static var exitCodeSuccess: Int32 {
  130. EXIT_SUCCESS
  131. }
  132. /// The code for exit with a general failure.
  133. static var exitCodeFailure: Int32 {
  134. EXIT_FAILURE
  135. }
  136. /// The code for exit with a validation failure.
  137. static var exitCodeValidationFailure: Int32 {
  138. #if os(Windows)
  139. return Int32(ERROR_BAD_ARGUMENTS)
  140. #elseif os(WASI)
  141. return EXIT_FAILURE
  142. #else
  143. return EX_USAGE
  144. #endif
  145. }
  146. }
  147. // MARK: Exit function
  148. extension Platform {
  149. /// Complete execution with the given exit code.
  150. static func exit(_ code: Int32) -> Never {
  151. #if canImport(Glibc)
  152. Glibc.exit(code)
  153. #elseif canImport(Musl)
  154. Musl.exit(code)
  155. #elseif canImport(Darwin)
  156. Darwin.exit(code)
  157. #elseif canImport(CRT)
  158. ucrt._exit(code)
  159. #elseif canImport(WASILibc)
  160. WASILibc.exit(code)
  161. #elseif canImport(Android)
  162. Android.exit(code)
  163. #endif
  164. }
  165. }
  166. // MARK: Standard error
  167. extension Platform {
  168. /// A type that represents the `stderr` output stream.
  169. struct StandardError: TextOutputStream {
  170. mutating func write(_ string: String) {
  171. for byte in string.utf8 { putc(numericCast(byte), stderr) }
  172. }
  173. }
  174. /// The `stderr` output stream.
  175. static var standardError: StandardError {
  176. StandardError()
  177. }
  178. }
  179. // MARK: Terminal size
  180. #if canImport(Glibc) || canImport(Android)
  181. func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
  182. ioctl(CInt(a), UInt(b), p)
  183. }
  184. #endif
  185. extension Platform {
  186. /// The default terminal size.
  187. private static var defaultTerminalSize: (width: Int, height: Int) {
  188. (width: 80, height: 25)
  189. }
  190. /// The terminal size specified by the COLUMNS and LINES overrides
  191. /// (if present).
  192. ///
  193. /// Per the [Linux environ(7) manpage][linenv]:
  194. ///
  195. /// ```
  196. /// * COLUMNS and LINES tell applications about the window size,
  197. /// possibly overriding the actual size.
  198. /// ```
  199. ///
  200. /// And the [FreeBSD environ(7) version][bsdenv]:
  201. ///
  202. /// ```
  203. /// COLUMNS The user's preferred width in column positions for the
  204. /// terminal. Utilities such as ls(1) and who(1) use this
  205. /// to format output into columns. If unset or empty,
  206. /// utilities will use an ioctl(2) call to ask the termi-
  207. /// nal driver for the width.
  208. /// ```
  209. ///
  210. /// > Note: Always returns `(nil, nil)` on Windows and WASI.
  211. ///
  212. /// - Returns: A tuple consisting of a width found in the `COLUMNS` environment
  213. /// variable (or `nil` if the variable is not present) and a height found in
  214. /// the `LINES` environment variable (or `nil` if that variable is not present).
  215. ///
  216. /// [linenv]: https://man7.org/linux/man-pages/man7/environ.7.html:~:text=COLUMNS
  217. /// [bsdenv]: https://man.freebsd.org/cgi/man.cgi?environ(7)#:~:text=COLUMNS
  218. private static func userSpecifiedTerminalSize() -> (width: Int?, height: Int?)
  219. {
  220. var width: Int? = nil
  221. var height: Int? = nil
  222. #if !os(Windows) && !os(WASI)
  223. if let columns = Platform.Environment[.columns, as: Int.self] {
  224. width = columns
  225. }
  226. if let lines = Platform.Environment[.lines, as: Int.self] {
  227. height = lines
  228. }
  229. #endif
  230. return (width: width, height: height)
  231. }
  232. /// The current terminal size as reported by the windowing system,
  233. /// if available.
  234. ///
  235. /// Returns (nil, nil) if no reported size is available.
  236. private static func reportedTerminalSize() -> (width: Int?, height: Int?) {
  237. #if os(WASI)
  238. // WASI doesn't yet support terminal size
  239. return (width: nil, height: nil)
  240. #elseif os(Windows)
  241. var csbi = CONSOLE_SCREEN_BUFFER_INFO()
  242. guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
  243. else {
  244. return (width: nil, height: nil)
  245. }
  246. return (
  247. width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1,
  248. height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1
  249. )
  250. #else
  251. var w = winsize()
  252. #if os(OpenBSD)
  253. // TIOCGWINSZ is a complex macro, so we need the flattened value.
  254. let tiocgwinsz = Int32(0x4008_7468)
  255. let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w)
  256. #elseif canImport(Musl)
  257. let err = ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w)
  258. #else
  259. let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)
  260. #endif
  261. guard err == 0 else { return (width: nil, height: nil) }
  262. let width = Int(w.ws_col)
  263. let height = Int(w.ws_row)
  264. return (
  265. width: width > 0 ? width : nil,
  266. height: height > 0 ? height : nil
  267. )
  268. #endif
  269. }
  270. /// Returns the current terminal size, or the default if the size is unavailable.
  271. static func terminalSize() -> (width: Int, height: Int) {
  272. let specifiedSize = self.userSpecifiedTerminalSize()
  273. // Avoid needlessly calling ioctl() if a complete override is in effect
  274. if let specifiedWidth = specifiedSize.width,
  275. let specifiedHeight = specifiedSize.height
  276. {
  277. return (width: specifiedWidth, height: specifiedHeight)
  278. }
  279. // Get the size self-reported by the terminal, if available
  280. let reportedSize = self.reportedTerminalSize()
  281. // As it isn't required that both width and height always be specified
  282. // together, either by the user or the terminal itself, they are
  283. // handled separately.
  284. return (
  285. width: specifiedSize.width ?? reportedSize.width
  286. ?? defaultTerminalSize.width,
  287. height: specifiedSize.height ?? reportedSize.height
  288. ?? defaultTerminalSize.height
  289. )
  290. }
  291. /// The current terminal size, or the default if the width is unavailable.
  292. static var terminalWidth: Int {
  293. self.terminalSize().width
  294. }
  295. }