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
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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 the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
import Common
public struct SwiftCWrapperTool {
let arguments: [String]
let environment: [String: String]
public init(arguments: [String] = CommandLine.arguments, environment: [String: String] = ProcessInfo.processInfo.environment) {
self.arguments = arguments
self.environment = environment
}
public func run() throws -> Int32 {
/// Non-default path to swiftc
let swiftcEnv = EnvOption("SK_STRESS_SWIFTC", type: String.self)
/// Non-default path to sk-stress-test
let stressTesterEnv = EnvOption("SK_STRESS_TEST", type: String.self)
/// Always return the same exit code as the underlying swiftc, even if stress testing uncovers failures
let ignoreIssuesEnv = EnvOption("SK_STRESS_SILENT", type: Bool.self)
/// Limit the number of sourcekit requests made per-file based on the number of AST rebuilds they trigger
let astBuildLimitEnv = EnvOption("SK_STRESS_AST_BUILD_LIMIT", type: Int.self)
/// A file in which measurements how long the SourceKit requests issued by the stress tester took, should be aggregated
let requestDurationsOutputFileEnv = EnvOption("SK_STRESS_REQUEST_DURATIONS_FILE", type: String.self)
/// Output only what the wrapped compiler outputs
let suppressOutputEnv = EnvOption("SK_STRESS_SUPPRESS_OUTPUT", type: Bool.self)
/// Non-default space-separated list of rewrite modes to use
let rewriteModesEnv = EnvOption("SK_STRESS_REWRITE_MODES", type: [RewriteMode].self)
/// Non-default space-separated list of request types to use
let requestKindsEnv = EnvOption("SK_STRESS_REQUESTS", type: [RequestKind].self)
/// Non-default space-separated list of protocol USRs to use for the ConformingMethodList request
let conformingMethodTypesEnv = EnvOption("SK_STRESS_CONFORMING_METHOD_TYPES", type: [String].self)
/// Limit the number of jobs
let maxJobsEnv = EnvOption("SK_STRESS_MAX_JOBS", type: Int.self)
/// Dump sourcekitd's responses to the supplied path
let dumpResponsesPathEnv = EnvOption("SK_STRESS_DUMP_RESPONSES_PATH", type: String.self)
/// Extra arguments to pass to code completion requests in the `key.codecomplete.options` dictionary.
/// Key and values are separated by `=`. Multiple options are separated by `,`.
let extraCodeCompleteOptionsEnv = EnvOption("SK_EXTRA_CODE_COMPLETE_OPTIONS", type: String.self)
// IssueManager params:
/// Non-default path to the json file containing expected failures
let expectedFailuresPathEnv = EnvOption("SK_XFAILS_PATH", type: String.self)
/// Non-default path to write the results json file to
let outputPathEnv = EnvOption("SK_STRESS_OUTPUT", type: String.self)
/// The value of the 'config' field to use when suggesting entries to add to the expected
/// failures json file to mark an unexpected failure as expected.
let activeConfigEnv = EnvOption("SK_STRESS_ACTIVE_CONFIG", type: String.self)
guard let swiftc = (try swiftcEnv.get(from: environment) ?? pathFromXcrun(for: "swiftc")) else {
throw EnvOptionError.noFallback(key: swiftcEnv.key, target: "swiftc")
}
guard let stressTester = (try stressTesterEnv.get(from: environment) ?? defaultStressTesterPath) else {
throw EnvOptionError.noFallback(key: stressTesterEnv.key, target: "sk-stress-test")
}
let ignoreIssues = try ignoreIssuesEnv.get(from: environment) ?? false
let suppressOutput = try suppressOutputEnv.get(from: environment) ?? false
let astBuildLimit = try astBuildLimitEnv.get(from: environment)
let requestDurationsOutputFile = try requestDurationsOutputFileEnv.get(from: environment).map(URL.init(fileURLWithPath:))
let rewriteModes = try rewriteModesEnv.get(from: environment) ?? [.none, .concurrent, .insideOut]
let requestKinds = RequestKind.reduce(try requestKindsEnv.get(from: environment) ?? [.ide])
let conformingMethodTypes = try conformingMethodTypesEnv.get(from: environment)
let maxJobs = try maxJobsEnv.get(from: environment)
let dumpResponsesPath = try dumpResponsesPathEnv.get(from: environment)
let extraCodeCompleteOptions = try extraCodeCompleteOptionsEnv.get(from: environment)
var issueManager: IssueManager? = nil
if let expectedFailuresPath = try expectedFailuresPathEnv.get(from: environment),
let outputPath = try outputPathEnv.get(from: environment),
let activeConfig = try activeConfigEnv.get(from: environment) {
issueManager = IssueManager(
activeConfig: activeConfig,
expectedIssuesFile: URL(fileURLWithPath: expectedFailuresPath, isDirectory: false),
resultsFile: URL(fileURLWithPath: outputPath, isDirectory: false),
activeRequests: requestKinds
)
}
let wrapper = SwiftCWrapper(swiftcArgs: Array(arguments.dropFirst()),
swiftcPath: swiftc,
stressTesterPath: stressTester,
astBuildLimit: astBuildLimit,
requestDurationsOutputFile: requestDurationsOutputFile,
rewriteModes: rewriteModes,
requestKinds: requestKinds,
conformingMethodTypes: conformingMethodTypes,
extraCodeCompleteOptions: extraCodeCompleteOptions?.split(separator: ",").map(String.init) ?? [],
ignoreIssues: ignoreIssues,
issueManager: issueManager,
maxJobs: maxJobs,
dumpResponsesPath: dumpResponsesPath,
failFast: true,
suppressOutput: suppressOutput)
return try wrapper.run()
}
var defaultStressTesterPath: String? {
let wrapperPath = URL(fileURLWithPath: arguments[0])
.deletingLastPathComponent()
.appendingPathComponent("sk-stress-test")
.path
guard FileManager.default.isExecutableFile(atPath: wrapperPath) else { return nil }
return wrapperPath
}
}
protocol EnvOptionKind: Equatable {
init(value: String, fromKey: String) throws
}
struct EnvOption<T: EnvOptionKind> {
let key: String
let type: T.Type
init(_ key: String, type: T.Type) {
self.key = key
self.type = type
}
func get(from environment: [String: String]) throws -> T? {
guard let value = environment[key] else {
return nil
}
return try type.init(value: value, fromKey: key)
}
}
public enum EnvOptionError: Swift.Error, CustomStringConvertible {
case typeMismatch(key: String, value: String, expectedType: Any.Type)
case unknownValue(key: String, value: String, validValues: [CustomStringConvertible])
case noFallback(key: String, target: String)
public var description: String {
switch self {
case .typeMismatch(let key, let value, let expectedType):
return "environment variable '\(key)' should have a value of type '\(expectedType)'; given '\(value)'"
case .noFallback(let key, let target):
return "couldn't locate \(target); please set environment variable \(key) to its path"
case .unknownValue(let key, let value, let validValues):
return "unknown value \(value) provided via environment variable \(key); should be one of: '\(validValues.map{ String(describing: $0)}.joined(separator: "', '"))'"
}
}
}
extension String: EnvOptionKind {
init(value: String, fromKey: String) throws { self.init(value) }
}
extension Int: EnvOptionKind {
init(value: String, fromKey key: String) throws {
guard let converted = Int(value) else {
throw EnvOptionError.typeMismatch(key: key, value: value, expectedType: Int.self)
}
self = converted
}
}
extension Bool: EnvOptionKind {
init(value: String, fromKey key: String) throws {
switch value.lowercased() {
case "true", "1":
self = true
case "false", "0":
self = false
default:
throw EnvOptionError.typeMismatch(key: key, value: value, expectedType: Bool.self)
}
}
}
extension RewriteMode: EnvOptionKind {
init(value: String, fromKey key: String) throws {
guard let mode = RewriteMode.allCases.first(where: { $0.rawValue.lowercased() == value.lowercased() }) else {
throw EnvOptionError.unknownValue(key: key, value: value, validValues: RewriteMode.allCases.map { $0.rawValue })
}
self = mode
}
}
extension Array: EnvOptionKind where Element: EnvOptionKind {
init(value: String, fromKey key: String) throws {
self = try value.split(separator: " ").map {
try Element.init(value: String($0), fromKey: key)
}
}
}
extension RequestKind: EnvOptionKind {
init(value: String, fromKey key: String) throws {
guard let kind = RequestKind.byName(value) else {
throw EnvOptionError.unknownValue(key: key, value: value, validValues: RequestKind.allCases.map { $0.rawValue })
}
self = kind
}
}
|