mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-12 08:13:26 +01:00
XCode project for app, moved app project to folder
This commit is contained in:
61
SideStoreApp/Sources/Cargo/Commands/Build.swift
Normal file
61
SideStoreApp/Sources/Cargo/Commands/Build.swift
Normal file
@@ -0,0 +1,61 @@
|
||||
import ArgumentParser
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct Build: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Print lint warnings and errors")
|
||||
|
||||
@OptionGroup
|
||||
var common: LintOrAnalyzeArguments
|
||||
@Option(help: pathOptionDescription(for: .build))
|
||||
var path: String?
|
||||
@Flag(help: quietOptionDescription(for: .build))
|
||||
var quiet = false
|
||||
@Option(help: "The directory of the cache used when linting.")
|
||||
var cachePath: String?
|
||||
@Flag(help: "Ignore cache when linting.")
|
||||
var noCache = false
|
||||
@Flag(help: "Run all rules, even opt-in and disabled ones, ignoring `only_rules`.")
|
||||
var enableAllRules = false
|
||||
@Argument(help: pathsArgumentDescription(for: .build))
|
||||
var paths = [String]()
|
||||
|
||||
func run() async throws {
|
||||
let allPaths: [String]
|
||||
if let path {
|
||||
queuedPrintError("""
|
||||
warning: The --path option is deprecated. Pass the path(s) to lint last to the swiftlint command.
|
||||
""")
|
||||
allPaths = [path] + paths
|
||||
} else if !paths.isEmpty {
|
||||
allPaths = paths
|
||||
} else {
|
||||
allPaths = [""] // Lint files in current working directory if no paths were specified.
|
||||
}
|
||||
let options = LintOrAnalyzeOptions(
|
||||
mode: .build,
|
||||
paths: allPaths,
|
||||
configurationFiles: common.config,
|
||||
strict: common.leniency == .strict,
|
||||
lenient: common.leniency == .lenient,
|
||||
forceExclude: common.forceExclude,
|
||||
useExcludingByPrefix: common.useAlternativeExcluding,
|
||||
useScriptInputFiles: common.useScriptInputFiles,
|
||||
benchmark: common.benchmark,
|
||||
reporter: common.reporter,
|
||||
quiet: quiet,
|
||||
output: common.output,
|
||||
progress: common.progress,
|
||||
cachePath: cachePath,
|
||||
ignoreCache: noCache,
|
||||
enableAllRules: enableAllRules,
|
||||
autocorrect: common.fix,
|
||||
format: common.format,
|
||||
compilerLogPath: nil,
|
||||
compileCommands: nil,
|
||||
inProcessSourcekit: common.inProcessSourcekit
|
||||
)
|
||||
try await LintOrAnalyzeCommand.run(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
SideStoreApp/Sources/Cargo/Commands/Cargo.swift
Normal file
22
SideStoreApp/Sources/Cargo/Commands/Cargo.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct Cargo: AsyncParsableCommand {
|
||||
static let configuration: CommandConfiguration = {
|
||||
if let directory = ProcessInfo.processInfo.environment["BUILD_WORKSPACE_DIRECTORY"] {
|
||||
FileManager.default.changeCurrentDirectoryPath(directory)
|
||||
}
|
||||
|
||||
return CommandConfiguration(
|
||||
commandName: "cargo",
|
||||
abstract: "A tool to build `rust` projects with `cargo`.",
|
||||
version: Version.value,
|
||||
subcommands: [
|
||||
Build.self,
|
||||
Version.self
|
||||
],
|
||||
defaultSubcommand: Build.self
|
||||
)
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
extension RulesFilter.ExcludingOptions {
|
||||
static func excludingOptions(byCommandLineOptions rulesFilterOptions: RulesFilterOptions) -> Self {
|
||||
var excludingOptions: Self = []
|
||||
|
||||
switch rulesFilterOptions.ruleEnablement {
|
||||
case .enabled:
|
||||
excludingOptions.insert(.disabled)
|
||||
case .disabled:
|
||||
excludingOptions.insert(.enabled)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
||||
if rulesFilterOptions.correctable {
|
||||
excludingOptions.insert(.uncorrectable)
|
||||
}
|
||||
|
||||
return excludingOptions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import ArgumentParser
|
||||
|
||||
enum RuleEnablementOptions: String, EnumerableFlag {
|
||||
case enabled, disabled
|
||||
|
||||
static func name(for value: RuleEnablementOptions) -> NameSpecification {
|
||||
return .shortAndLong
|
||||
}
|
||||
|
||||
static func help(for value: RuleEnablementOptions) -> ArgumentHelp? {
|
||||
return "Only show \(value.rawValue) rules"
|
||||
}
|
||||
}
|
||||
|
||||
struct RulesFilterOptions: ParsableArguments {
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var ruleEnablement: RuleEnablementOptions?
|
||||
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
||||
var correctable = false
|
||||
}
|
||||
23
SideStoreApp/Sources/Cargo/Commands/Version.swift
Normal file
23
SideStoreApp/Sources/Cargo/Commands/Version.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
import ArgumentParser
|
||||
import SwiftLintFramework
|
||||
|
||||
extension Cargo {
|
||||
struct Version: ParsableCommand {
|
||||
@Flag(help: "Display full version info")
|
||||
var verbose = false
|
||||
|
||||
static let configuration = CommandConfiguration(abstract: "Display the current version of Cargo")
|
||||
|
||||
static var value: String { "TODO" }
|
||||
|
||||
func run() throws {
|
||||
if verbose, let buildID = ExecutableInfo.buildID {
|
||||
print("Version:", Self.value)
|
||||
print("Build ID:", buildID)
|
||||
} else {
|
||||
print(Self.value)
|
||||
}
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
61
SideStoreApp/Sources/Cargo/swiftlint/Commands/Analyze.swift
Normal file
61
SideStoreApp/Sources/Cargo/swiftlint/Commands/Analyze.swift
Normal file
@@ -0,0 +1,61 @@
|
||||
import ArgumentParser
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct Analyze: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Run analysis rules")
|
||||
|
||||
@OptionGroup
|
||||
var common: LintOrAnalyzeArguments
|
||||
@Option(help: pathOptionDescription(for: .analyze))
|
||||
var path: String?
|
||||
@Flag(help: quietOptionDescription(for: .analyze))
|
||||
var quiet = false
|
||||
@Option(help: "The path of the full xcodebuild log to use when running AnalyzerRules.")
|
||||
var compilerLogPath: String?
|
||||
@Option(help: "The path of a compilation database to use when running AnalyzerRules.")
|
||||
var compileCommands: String?
|
||||
@Argument(help: pathsArgumentDescription(for: .analyze))
|
||||
var paths = [String]()
|
||||
|
||||
func run() async throws {
|
||||
let allPaths: [String]
|
||||
if let path {
|
||||
queuedPrintError("""
|
||||
warning: The --path option is deprecated. Pass the path(s) to analyze last to the swiftlint command.
|
||||
""")
|
||||
allPaths = [path] + paths
|
||||
} else if !paths.isEmpty {
|
||||
allPaths = paths
|
||||
} else {
|
||||
allPaths = [""] // Analyze files in current working directory if no paths were specified.
|
||||
}
|
||||
let options = LintOrAnalyzeOptions(
|
||||
mode: .analyze,
|
||||
paths: allPaths,
|
||||
useSTDIN: false,
|
||||
configurationFiles: common.config,
|
||||
strict: common.leniency == .strict,
|
||||
lenient: common.leniency == .lenient,
|
||||
forceExclude: common.forceExclude,
|
||||
useExcludingByPrefix: common.useAlternativeExcluding,
|
||||
useScriptInputFiles: common.useScriptInputFiles,
|
||||
benchmark: common.benchmark,
|
||||
reporter: common.reporter,
|
||||
quiet: quiet,
|
||||
output: common.output,
|
||||
progress: common.progress,
|
||||
cachePath: nil,
|
||||
ignoreCache: true,
|
||||
enableAllRules: false,
|
||||
autocorrect: common.fix,
|
||||
format: common.format,
|
||||
compilerLogPath: compilerLogPath,
|
||||
compileCommands: compileCommands,
|
||||
inProcessSourcekit: common.inProcessSourcekit
|
||||
)
|
||||
|
||||
try await LintOrAnalyzeCommand.run(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
extension RulesFilter.ExcludingOptions {
|
||||
static func excludingOptions(byCommandLineOptions rulesFilterOptions: RulesFilterOptions) -> Self {
|
||||
var excludingOptions: Self = []
|
||||
|
||||
switch rulesFilterOptions.ruleEnablement {
|
||||
case .enabled:
|
||||
excludingOptions.insert(.disabled)
|
||||
case .disabled:
|
||||
excludingOptions.insert(.enabled)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
||||
if rulesFilterOptions.correctable {
|
||||
excludingOptions.insert(.uncorrectable)
|
||||
}
|
||||
|
||||
return excludingOptions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import ArgumentParser
|
||||
|
||||
enum RuleEnablementOptions: String, EnumerableFlag {
|
||||
case enabled, disabled
|
||||
|
||||
static func name(for value: RuleEnablementOptions) -> NameSpecification {
|
||||
return .shortAndLong
|
||||
}
|
||||
|
||||
static func help(for value: RuleEnablementOptions) -> ArgumentHelp? {
|
||||
return "Only show \(value.rawValue) rules"
|
||||
}
|
||||
}
|
||||
|
||||
struct RulesFilterOptions: ParsableArguments {
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var ruleEnablement: RuleEnablementOptions?
|
||||
@Flag(name: .shortAndLong, help: "Only display correctable rules")
|
||||
var correctable = false
|
||||
}
|
||||
43
SideStoreApp/Sources/Cargo/swiftlint/Commands/Docs.swift
Normal file
43
SideStoreApp/Sources/Cargo/swiftlint/Commands/Docs.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct Docs: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Open SwiftLint documentation website in the default web browser"
|
||||
)
|
||||
|
||||
@Argument(help: "The identifier of the rule to open the documentation for")
|
||||
var ruleID: String?
|
||||
|
||||
func run() throws {
|
||||
var subPage = ""
|
||||
if let ruleID {
|
||||
if primaryRuleList.list[ruleID] == nil {
|
||||
queuedPrintError("There is no rule named '\(ruleID)'. Opening rule directory instead.")
|
||||
subPage = "rule-directory.html"
|
||||
} else {
|
||||
subPage = ruleID + ".html"
|
||||
}
|
||||
}
|
||||
open(URL(string: "https://realm.github.io/SwiftLint/\(subPage)")!)
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func open(_ url: URL) {
|
||||
let process = Process()
|
||||
#if os(Linux)
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/env", isDirectory: false)
|
||||
let command = "xdg-open"
|
||||
process.arguments = [command, url.absoluteString]
|
||||
try? process.run()
|
||||
#else
|
||||
process.launchPath = "/usr/bin/env"
|
||||
let command = "open"
|
||||
process.arguments = [command, url.absoluteString]
|
||||
process.launch()
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct GenerateDocs: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "Generates markdown documentation for selected group of rules"
|
||||
)
|
||||
|
||||
@Option(help: "The directory where the documentation should be saved")
|
||||
var path = "rule_docs"
|
||||
@Option(help: "The path to a SwiftLint configuration file")
|
||||
var config: String?
|
||||
@OptionGroup
|
||||
var rulesFilterOptions: RulesFilterOptions
|
||||
|
||||
func run() throws {
|
||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||
let rulesFilter = RulesFilter(enabledRules: configuration.rules)
|
||||
let rules = rulesFilter.getRules(excluding: .excludingOptions(byCommandLineOptions: rulesFilterOptions))
|
||||
|
||||
try RuleListDocumentation(rules)
|
||||
.write(to: URL(fileURLWithPath: path, isDirectory: true))
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
64
SideStoreApp/Sources/Cargo/swiftlint/Commands/Lint.swift
Normal file
64
SideStoreApp/Sources/Cargo/swiftlint/Commands/Lint.swift
Normal file
@@ -0,0 +1,64 @@
|
||||
import ArgumentParser
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct Lint: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Print lint warnings and errors")
|
||||
|
||||
@OptionGroup
|
||||
var common: LintOrAnalyzeArguments
|
||||
@Option(help: pathOptionDescription(for: .lint))
|
||||
var path: String?
|
||||
@Flag(help: "Lint standard input.")
|
||||
var useSTDIN = false
|
||||
@Flag(help: quietOptionDescription(for: .lint))
|
||||
var quiet = false
|
||||
@Option(help: "The directory of the cache used when linting.")
|
||||
var cachePath: String?
|
||||
@Flag(help: "Ignore cache when linting.")
|
||||
var noCache = false
|
||||
@Flag(help: "Run all rules, even opt-in and disabled ones, ignoring `only_rules`.")
|
||||
var enableAllRules = false
|
||||
@Argument(help: pathsArgumentDescription(for: .lint))
|
||||
var paths = [String]()
|
||||
|
||||
func run() async throws {
|
||||
let allPaths: [String]
|
||||
if let path {
|
||||
queuedPrintError("""
|
||||
warning: The --path option is deprecated. Pass the path(s) to lint last to the swiftlint command.
|
||||
""")
|
||||
allPaths = [path] + paths
|
||||
} else if !paths.isEmpty {
|
||||
allPaths = paths
|
||||
} else {
|
||||
allPaths = [""] // Lint files in current working directory if no paths were specified.
|
||||
}
|
||||
let options = LintOrAnalyzeOptions(
|
||||
mode: .lint,
|
||||
paths: allPaths,
|
||||
useSTDIN: useSTDIN,
|
||||
configurationFiles: common.config,
|
||||
strict: common.leniency == .strict,
|
||||
lenient: common.leniency == .lenient,
|
||||
forceExclude: common.forceExclude,
|
||||
useExcludingByPrefix: common.useAlternativeExcluding,
|
||||
useScriptInputFiles: common.useScriptInputFiles,
|
||||
benchmark: common.benchmark,
|
||||
reporter: common.reporter,
|
||||
quiet: quiet,
|
||||
output: common.output,
|
||||
progress: common.progress,
|
||||
cachePath: cachePath,
|
||||
ignoreCache: noCache,
|
||||
enableAllRules: enableAllRules,
|
||||
autocorrect: common.fix,
|
||||
format: common.format,
|
||||
compilerLogPath: nil,
|
||||
compileCommands: nil,
|
||||
inProcessSourcekit: common.inProcessSourcekit
|
||||
)
|
||||
try await LintOrAnalyzeCommand.run(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
123
SideStoreApp/Sources/Cargo/swiftlint/Commands/Rules.swift
Normal file
123
SideStoreApp/Sources/Cargo/swiftlint/Commands/Rules.swift
Normal file
@@ -0,0 +1,123 @@
|
||||
import ArgumentParser
|
||||
#if canImport(Darwin)
|
||||
import Darwin
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
#else
|
||||
#error("Unsupported platform")
|
||||
#endif
|
||||
import Foundation
|
||||
@_spi(TestHelper)
|
||||
import SwiftLintFramework
|
||||
import SwiftyTextTable
|
||||
|
||||
extension SwiftLint {
|
||||
struct Rules: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Display the list of rules and their identifiers")
|
||||
|
||||
@Option(help: "The path to a SwiftLint configuration file")
|
||||
var config: String?
|
||||
@OptionGroup
|
||||
var rulesFilterOptions: RulesFilterOptions
|
||||
@Flag(name: .shortAndLong, help: "Display full configuration details")
|
||||
var verbose = false
|
||||
@Argument(help: "The rule identifier to display description for")
|
||||
var ruleID: String?
|
||||
|
||||
func run() throws {
|
||||
if let ruleID {
|
||||
guard let rule = primaryRuleList.list[ruleID] else {
|
||||
throw SwiftLintError.usageError(description: "No rule with identifier: \(ruleID)")
|
||||
}
|
||||
|
||||
rule.description.printDescription()
|
||||
return
|
||||
}
|
||||
|
||||
let configuration = Configuration(configurationFiles: [config].compactMap({ $0 }))
|
||||
let rulesFilter = RulesFilter(enabledRules: configuration.rules)
|
||||
let rules = rulesFilter.getRules(excluding: .excludingOptions(byCommandLineOptions: rulesFilterOptions))
|
||||
let table = TextTable(ruleList: rules, configuration: configuration, verbose: verbose)
|
||||
print(table.render())
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension RuleDescription {
|
||||
func printDescription() {
|
||||
print("\(consoleDescription)")
|
||||
|
||||
guard !triggeringExamples.isEmpty else { return }
|
||||
|
||||
func indent(_ string: String) -> String {
|
||||
return string.components(separatedBy: "\n")
|
||||
.map { " \($0)" }
|
||||
.joined(separator: "\n")
|
||||
}
|
||||
print("\nTriggering Examples (violation is marked with '↓'):")
|
||||
for (index, example) in triggeringExamples.enumerated() {
|
||||
print("\nExample #\(index + 1)\n\n\(indent(example.code))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SwiftyTextTable
|
||||
|
||||
private extension TextTable {
|
||||
init(ruleList: RuleList, configuration: Configuration, verbose: Bool) {
|
||||
let columns = [
|
||||
TextTableColumn(header: "identifier"),
|
||||
TextTableColumn(header: "opt-in"),
|
||||
TextTableColumn(header: "correctable"),
|
||||
TextTableColumn(header: "enabled in your config"),
|
||||
TextTableColumn(header: "kind"),
|
||||
TextTableColumn(header: "analyzer"),
|
||||
TextTableColumn(header: "uses sourcekit"),
|
||||
TextTableColumn(header: "configuration")
|
||||
]
|
||||
self.init(columns: columns)
|
||||
let sortedRules = ruleList.list.sorted { $0.0 < $1.0 }
|
||||
func truncate(_ string: String) -> String {
|
||||
let stringWithNoNewlines = string.replacingOccurrences(of: "\n", with: "\\n")
|
||||
let minWidth = "configuration".count - "...".count
|
||||
let configurationStartColumn = 140
|
||||
let maxWidth = verbose ? Int.max : Terminal.currentWidth()
|
||||
let truncatedEndIndex = stringWithNoNewlines.index(
|
||||
stringWithNoNewlines.startIndex,
|
||||
offsetBy: max(minWidth, maxWidth - configurationStartColumn),
|
||||
limitedBy: stringWithNoNewlines.endIndex
|
||||
)
|
||||
if let truncatedEndIndex {
|
||||
return stringWithNoNewlines[..<truncatedEndIndex] + "..."
|
||||
}
|
||||
return stringWithNoNewlines
|
||||
}
|
||||
for (ruleID, ruleType) in sortedRules {
|
||||
let rule = ruleType.init()
|
||||
let configuredRule = configuration.configuredRule(forID: ruleID)
|
||||
addRow(values: [
|
||||
ruleID,
|
||||
(rule is OptInRule) ? "yes" : "no",
|
||||
(rule is CorrectableRule) ? "yes" : "no",
|
||||
configuredRule != nil ? "yes" : "no",
|
||||
ruleType.description.kind.rawValue,
|
||||
(rule is AnalyzerRule) ? "yes" : "no",
|
||||
(rule is SourceKitFreeRule) ? "no" : "yes",
|
||||
truncate((configuredRule ?? rule).configurationDescription)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct Terminal {
|
||||
static func currentWidth() -> Int {
|
||||
var size = winsize()
|
||||
#if os(Linux)
|
||||
_ = ioctl(CInt(STDOUT_FILENO), UInt(TIOCGWINSZ), &size)
|
||||
#else
|
||||
_ = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)
|
||||
#endif
|
||||
return Int(size.ws_col)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct SwiftLint: AsyncParsableCommand {
|
||||
static let configuration: CommandConfiguration = {
|
||||
if let directory = ProcessInfo.processInfo.environment["BUILD_WORKSPACE_DIRECTORY"] {
|
||||
FileManager.default.changeCurrentDirectoryPath(directory)
|
||||
}
|
||||
|
||||
return CommandConfiguration(
|
||||
commandName: "swiftlint",
|
||||
abstract: "A tool to enforce Swift style and conventions.",
|
||||
version: Version.value,
|
||||
subcommands: [
|
||||
Analyze.self,
|
||||
Docs.self,
|
||||
GenerateDocs.self,
|
||||
Lint.self,
|
||||
Rules.self,
|
||||
Version.self
|
||||
],
|
||||
defaultSubcommand: Lint.self
|
||||
)
|
||||
}()
|
||||
}
|
||||
23
SideStoreApp/Sources/Cargo/swiftlint/Commands/Version.swift
Normal file
23
SideStoreApp/Sources/Cargo/swiftlint/Commands/Version.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
import ArgumentParser
|
||||
import SwiftLintFramework
|
||||
|
||||
extension SwiftLint {
|
||||
struct Version: ParsableCommand {
|
||||
@Flag(help: "Display full version info")
|
||||
var verbose = false
|
||||
|
||||
static let configuration = CommandConfiguration(abstract: "Display the current version of SwiftLint")
|
||||
|
||||
static var value: String { SwiftLintFramework.Version.current.value }
|
||||
|
||||
func run() throws {
|
||||
if verbose, let buildID = ExecutableInfo.buildID {
|
||||
print("Version:", Self.value)
|
||||
print("Build ID:", buildID)
|
||||
} else {
|
||||
print(Self.value)
|
||||
}
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
import CollectionConcurrencyKit
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftLintFramework
|
||||
|
||||
private actor CounterActor {
|
||||
private var count = 0
|
||||
|
||||
func next() -> Int {
|
||||
count += 1
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
private func scriptInputFiles() throws -> [SwiftLintFile] {
|
||||
let inputFileKey = "SCRIPT_INPUT_FILE_COUNT"
|
||||
guard let countString = ProcessInfo.processInfo.environment[inputFileKey] else {
|
||||
throw SwiftLintError.usageError(description: "\(inputFileKey) variable not set")
|
||||
}
|
||||
|
||||
guard let count = Int(countString) else {
|
||||
throw SwiftLintError.usageError(description: "\(inputFileKey) did not specify a number")
|
||||
}
|
||||
|
||||
return (0..<count).compactMap { fileNumber in
|
||||
do {
|
||||
let environment = ProcessInfo.processInfo.environment
|
||||
let variable = "SCRIPT_INPUT_FILE_\(fileNumber)"
|
||||
guard let path = environment[variable] else {
|
||||
throw SwiftLintError.usageError(description: "Environment variable not set: \(variable)")
|
||||
}
|
||||
if path.bridge().isSwiftFile() {
|
||||
return SwiftLintFile(pathDeferringReading: path)
|
||||
}
|
||||
return nil
|
||||
} catch {
|
||||
queuedPrintError(String(describing: error))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
private func autoreleasepool<T>(block: () -> T) -> T { return block() }
|
||||
#endif
|
||||
|
||||
extension Configuration {
|
||||
func visitLintableFiles(with visitor: LintableFilesVisitor, storage: RuleStorage) async throws -> [SwiftLintFile] {
|
||||
let files = try await Signposts.record(name: "Configuration.VisitLintableFiles.GetFiles") {
|
||||
try await getFiles(with: visitor)
|
||||
}
|
||||
let groupedFiles = try Signposts.record(name: "Configuration.VisitLintableFiles.GroupFiles") {
|
||||
try groupFiles(files, visitor: visitor)
|
||||
}
|
||||
let lintersForFile = Signposts.record(name: "Configuration.VisitLintableFiles.LintersForFile") {
|
||||
groupedFiles.map { file in
|
||||
linters(for: [file.key: file.value], visitor: visitor)
|
||||
}
|
||||
}
|
||||
let duplicateFileNames = Signposts.record(name: "Configuration.VisitLintableFiles.DuplicateFileNames") {
|
||||
lintersForFile.map(\.duplicateFileNames)
|
||||
}
|
||||
let collected = await Signposts.record(name: "Configuration.VisitLintableFiles.Collect") {
|
||||
await zip(lintersForFile, duplicateFileNames).asyncMap { linters, duplicateFileNames in
|
||||
await collect(linters: linters, visitor: visitor, storage: storage,
|
||||
duplicateFileNames: duplicateFileNames)
|
||||
}
|
||||
}
|
||||
let result = await Signposts.record(name: "Configuration.VisitLintableFiles.Visit") {
|
||||
await collected.asyncMap { linters, duplicateFileNames in
|
||||
await visit(linters: linters, visitor: visitor, duplicateFileNames: duplicateFileNames)
|
||||
}
|
||||
}
|
||||
return result.flatMap { $0 }
|
||||
}
|
||||
|
||||
private func groupFiles(_ files: [SwiftLintFile], visitor: LintableFilesVisitor) throws
|
||||
-> [Configuration: [SwiftLintFile]] {
|
||||
if files.isEmpty && !visitor.allowZeroLintableFiles {
|
||||
throw SwiftLintError.usageError(
|
||||
description: "No lintable files found at paths: '\(visitor.paths.joined(separator: ", "))'"
|
||||
)
|
||||
}
|
||||
|
||||
var groupedFiles = [Configuration: [SwiftLintFile]]()
|
||||
for file in files {
|
||||
let fileConfiguration = configuration(for: file)
|
||||
let fileConfigurationRootPath = fileConfiguration.rootDirectory.bridge()
|
||||
|
||||
// Files whose configuration specifies they should be excluded will be skipped
|
||||
let shouldSkip = fileConfiguration.excludedPaths.contains { excludedRelativePath in
|
||||
let excludedPath = fileConfigurationRootPath.appendingPathComponent(excludedRelativePath)
|
||||
let filePathComponents = file.path?.bridge().pathComponents ?? []
|
||||
let excludedPathComponents = excludedPath.bridge().pathComponents
|
||||
return filePathComponents.starts(with: excludedPathComponents)
|
||||
}
|
||||
|
||||
if !shouldSkip {
|
||||
groupedFiles[fileConfiguration, default: []].append(file)
|
||||
}
|
||||
}
|
||||
|
||||
return groupedFiles
|
||||
}
|
||||
|
||||
private func outputFilename(for path: String, duplicateFileNames: Set<String>) -> String {
|
||||
let basename = path.bridge().lastPathComponent
|
||||
if !duplicateFileNames.contains(basename) {
|
||||
return basename
|
||||
}
|
||||
|
||||
var pathComponents = path.bridge().pathComponents
|
||||
for component in rootDirectory.bridge().pathComponents where pathComponents.first == component {
|
||||
pathComponents.removeFirst()
|
||||
}
|
||||
|
||||
return pathComponents.joined(separator: "/")
|
||||
}
|
||||
|
||||
private func linters(for filesPerConfiguration: [Configuration: [SwiftLintFile]],
|
||||
visitor: LintableFilesVisitor) -> [Linter] {
|
||||
let fileCount = filesPerConfiguration.reduce(0) { $0 + $1.value.count }
|
||||
|
||||
var linters = [Linter]()
|
||||
linters.reserveCapacity(fileCount)
|
||||
for (config, files) in filesPerConfiguration {
|
||||
let newConfig: Configuration
|
||||
if visitor.cache != nil {
|
||||
newConfig = config.withPrecomputedCacheDescription()
|
||||
} else {
|
||||
newConfig = config
|
||||
}
|
||||
linters += files.map { visitor.linter(forFile: $0, configuration: newConfig) }
|
||||
}
|
||||
return linters
|
||||
}
|
||||
|
||||
private func collect(linters: [Linter],
|
||||
visitor: LintableFilesVisitor,
|
||||
storage: RuleStorage,
|
||||
duplicateFileNames: Set<String>) async -> ([CollectedLinter], Set<String>) {
|
||||
let counter = CounterActor()
|
||||
let total = linters.filter(\.isCollecting).count
|
||||
let progress = ProgressBar(count: total)
|
||||
if visitor.showProgressBar && total > 0 {
|
||||
await progress.initialize()
|
||||
}
|
||||
let collect = { (linter: Linter) -> CollectedLinter? in
|
||||
let skipFile = visitor.shouldSkipFile(atPath: linter.file.path)
|
||||
if !visitor.quiet && linter.isCollecting {
|
||||
if visitor.showProgressBar {
|
||||
await progress.printNext()
|
||||
} else if let filePath = linter.file.path {
|
||||
let outputFilename = self.outputFilename(for: filePath, duplicateFileNames: duplicateFileNames)
|
||||
let collected = await counter.next()
|
||||
if skipFile {
|
||||
queuedPrintError("""
|
||||
Skipping '\(outputFilename)' (\(collected)/\(total)) \
|
||||
because its compiler arguments could not be found
|
||||
""")
|
||||
} else {
|
||||
queuedPrintError("Collecting '\(outputFilename)' (\(collected)/\(total))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard !skipFile else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return autoreleasepool {
|
||||
linter.collect(into: storage)
|
||||
}
|
||||
}
|
||||
|
||||
let collectedLinters = await visitor.parallel ?
|
||||
linters.concurrentCompactMap(collect) :
|
||||
linters.asyncCompactMap(collect)
|
||||
return (collectedLinters, duplicateFileNames)
|
||||
}
|
||||
|
||||
private func visit(linters: [CollectedLinter],
|
||||
visitor: LintableFilesVisitor,
|
||||
duplicateFileNames: Set<String>) async -> [SwiftLintFile] {
|
||||
let counter = CounterActor()
|
||||
let progress = ProgressBar(count: linters.count)
|
||||
if visitor.showProgressBar {
|
||||
await progress.initialize()
|
||||
}
|
||||
let visit = { (linter: CollectedLinter) -> SwiftLintFile in
|
||||
if !visitor.quiet {
|
||||
if visitor.showProgressBar {
|
||||
await progress.printNext()
|
||||
} else if let filePath = linter.file.path {
|
||||
let outputFilename = self.outputFilename(for: filePath, duplicateFileNames: duplicateFileNames)
|
||||
let visited = await counter.next()
|
||||
queuedPrintError("\(visitor.action) '\(outputFilename)' (\(visited)/\(linters.count))")
|
||||
}
|
||||
}
|
||||
|
||||
await Signposts.record(name: "Configuration.Visit", span: .file(linter.file.path ?? "")) {
|
||||
await visitor.block(linter)
|
||||
}
|
||||
return linter.file
|
||||
}
|
||||
return await visitor.parallel ?
|
||||
linters.concurrentMap(visit) :
|
||||
linters.asyncMap(visit)
|
||||
}
|
||||
|
||||
fileprivate func getFiles(with visitor: LintableFilesVisitor) async throws -> [SwiftLintFile] {
|
||||
if visitor.useSTDIN {
|
||||
let stdinData = FileHandle.standardInput.readDataToEndOfFile()
|
||||
if let stdinString = String(data: stdinData, encoding: .utf8) {
|
||||
return [SwiftLintFile(contents: stdinString)]
|
||||
}
|
||||
throw SwiftLintError.usageError(description: "stdin isn't a UTF8-encoded string")
|
||||
} else if visitor.useScriptInputFiles {
|
||||
let files = try scriptInputFiles()
|
||||
guard visitor.forceExclude else {
|
||||
return files
|
||||
}
|
||||
|
||||
let scriptInputPaths = files.compactMap { $0.path }
|
||||
let filesToLint = visitor.useExcludingByPrefix ?
|
||||
filterExcludedPathsByPrefix(in: scriptInputPaths) :
|
||||
filterExcludedPaths(in: scriptInputPaths)
|
||||
return filesToLint.map(SwiftLintFile.init(pathDeferringReading:))
|
||||
}
|
||||
if !visitor.quiet {
|
||||
let filesInfo: String
|
||||
if visitor.paths.isEmpty || visitor.paths == [""] {
|
||||
filesInfo = "in current working directory"
|
||||
} else {
|
||||
filesInfo = "at paths \(visitor.paths.joined(separator: ", "))"
|
||||
}
|
||||
|
||||
queuedPrintError("\(visitor.action) Swift files \(filesInfo)")
|
||||
}
|
||||
return visitor.paths.flatMap {
|
||||
self.lintableFiles(inPath: $0, forceExclude: visitor.forceExclude,
|
||||
excludeByPrefix: visitor.useExcludingByPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
func visitLintableFiles(options: LintOrAnalyzeOptions, cache: LinterCache? = nil, storage: RuleStorage,
|
||||
visitorBlock: @escaping (CollectedLinter) async -> Void) async throws -> [SwiftLintFile] {
|
||||
let visitor = try LintableFilesVisitor.create(options, cache: cache,
|
||||
allowZeroLintableFiles: allowZeroLintableFiles,
|
||||
block: visitorBlock)
|
||||
return try await visitLintableFiles(with: visitor, storage: storage)
|
||||
}
|
||||
|
||||
// MARK: LintOrAnalyze Command
|
||||
|
||||
init(options: LintOrAnalyzeOptions) {
|
||||
self.init(
|
||||
configurationFiles: options.configurationFiles,
|
||||
enableAllRules: options.enableAllRules,
|
||||
cachePath: options.cachePath
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct DuplicateCollector {
|
||||
var all = Set<String>()
|
||||
var duplicates = Set<String>()
|
||||
}
|
||||
|
||||
private extension Collection where Element == Linter {
|
||||
var duplicateFileNames: Set<String> {
|
||||
let collector = reduce(into: DuplicateCollector()) { result, linter in
|
||||
if let filename = linter.file.path?.bridge().lastPathComponent {
|
||||
if result.all.contains(filename) {
|
||||
result.duplicates.insert(filename)
|
||||
}
|
||||
|
||||
result.all.insert(filename)
|
||||
}
|
||||
}
|
||||
return collector.duplicates
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
|
||||
extension ProcessInfo {
|
||||
var isLikelyXcodeCloudEnvironment: Bool {
|
||||
// https://developer.apple.com/documentation/xcode/environment-variable-reference
|
||||
let requiredKeys: Set = [
|
||||
"CI",
|
||||
"CI_BUILD_ID",
|
||||
"CI_BUILD_NUMBER",
|
||||
"CI_BUNDLE_ID",
|
||||
"CI_COMMIT",
|
||||
"CI_DERIVED_DATA_PATH",
|
||||
"CI_PRODUCT",
|
||||
"CI_PRODUCT_ID",
|
||||
"CI_PRODUCT_PLATFORM",
|
||||
"CI_PROJECT_FILE_PATH",
|
||||
"CI_START_CONDITION",
|
||||
"CI_TEAM_ID",
|
||||
"CI_WORKFLOW",
|
||||
"CI_WORKSPACE",
|
||||
"CI_XCODE_PROJECT",
|
||||
"CI_XCODE_SCHEME",
|
||||
"CI_XCODEBUILD_ACTION"
|
||||
]
|
||||
|
||||
return requiredKeys.isSubset(of: environment.keys)
|
||||
}
|
||||
}
|
||||
52
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Benchmark.swift
Normal file
52
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Benchmark.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
struct BenchmarkEntry {
|
||||
let id: String
|
||||
let time: Double
|
||||
}
|
||||
|
||||
struct Benchmark {
|
||||
private let name: String
|
||||
private var entries = [BenchmarkEntry]()
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
mutating func record(id: String, time: Double) {
|
||||
guard id != "custom_rules" else { return }
|
||||
entries.append(BenchmarkEntry(id: id, time: time))
|
||||
}
|
||||
|
||||
mutating func record(file: SwiftLintFile, from start: Date) {
|
||||
record(id: file.path ?? "<nopath>", time: -start.timeIntervalSinceNow)
|
||||
}
|
||||
|
||||
func save() {
|
||||
// Decomposed to improve compile times
|
||||
let entriesDict: [String: Double] = entries.reduce(into: [String: Double]()) { accu, idAndTime in
|
||||
accu[idAndTime.id] = (accu[idAndTime.id] ?? 0) + idAndTime.time
|
||||
}
|
||||
let entriesKeyValues: [(String, Double)] = entriesDict.sorted { $0.1 < $1.1 }
|
||||
let lines: [String] = entriesKeyValues.map { id, time -> String in
|
||||
return "\(numberFormatter.string(from: NSNumber(value: time))!): \(id)"
|
||||
}
|
||||
let string: String = lines.joined(separator: "\n") + "\n"
|
||||
let url = URL(fileURLWithPath: "benchmark_\(name)_\(timestamp).txt", isDirectory: false)
|
||||
try? string.data(using: .utf8)?.write(to: url, options: [.atomic])
|
||||
}
|
||||
}
|
||||
|
||||
private let numberFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.minimumFractionDigits = 3
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private let timestamp: String = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy_MM_dd_HH_mm_ss"
|
||||
return formatter.string(from: Date())
|
||||
}()
|
||||
@@ -0,0 +1,108 @@
|
||||
import Foundation
|
||||
|
||||
struct CompilerArgumentsExtractor {
|
||||
static func allCompilerInvocations(compilerLogs: String) -> [[String]] {
|
||||
var compilerInvocations = [[String]]()
|
||||
compilerLogs.enumerateLines { line, _ in
|
||||
if let swiftcIndex = line.range(of: "swiftc ")?.upperBound, line.contains(" -module-name ") {
|
||||
let invocation = parseCLIArguments(String(line[swiftcIndex...]))
|
||||
.expandingResponseFiles
|
||||
.filteringCompilerArguments
|
||||
compilerInvocations.append(invocation)
|
||||
}
|
||||
}
|
||||
return compilerInvocations
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func parseCLIArguments(_ string: String) -> [String] {
|
||||
let escapedSpacePlaceholder = "\u{0}"
|
||||
let scanner = Scanner(string: string)
|
||||
var str = ""
|
||||
var didStart = false
|
||||
while let result = scanner.scanUpToString("\"") {
|
||||
if didStart {
|
||||
str += result.replacingOccurrences(of: " ", with: escapedSpacePlaceholder)
|
||||
str += " "
|
||||
} else {
|
||||
str += result
|
||||
}
|
||||
_ = scanner.scanString("\"")
|
||||
didStart.toggle()
|
||||
}
|
||||
return str.trimmingCharacters(in: .whitespaces)
|
||||
.replacingOccurrences(of: "\\ ", with: escapedSpacePlaceholder)
|
||||
.components(separatedBy: " ")
|
||||
.map { $0.replacingOccurrences(of: escapedSpacePlaceholder, with: " ") }
|
||||
}
|
||||
|
||||
/**
|
||||
Partially filters compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept.
|
||||
|
||||
- parameter args: Compiler arguments, as parsed from `xcodebuild`.
|
||||
|
||||
- returns: A tuple of partially filtered compiler arguments in `.0`, and whether or not there are
|
||||
more flags to remove in `.1`.
|
||||
*/
|
||||
private func partiallyFilter(arguments args: [String]) -> ([String], Bool) {
|
||||
guard let indexOfFlagToRemove = args.firstIndex(of: "-output-file-map") else {
|
||||
return (args, false)
|
||||
}
|
||||
var args = args
|
||||
args.remove(at: args.index(after: indexOfFlagToRemove))
|
||||
args.remove(at: indexOfFlagToRemove)
|
||||
return (args, true)
|
||||
}
|
||||
|
||||
extension Array where Element == String {
|
||||
/// Return the full list of compiler arguments, replacing any response files with their contents.
|
||||
fileprivate var expandingResponseFiles: [String] {
|
||||
return flatMap { arg -> [String] in
|
||||
guard arg.starts(with: "@") else {
|
||||
return [arg]
|
||||
}
|
||||
let responseFile = String(arg.dropFirst())
|
||||
return (try? String(contentsOf: URL(fileURLWithPath: responseFile, isDirectory: false))).flatMap {
|
||||
$0.trimmingCharacters(in: .newlines)
|
||||
.components(separatedBy: "\n")
|
||||
.expandingResponseFiles
|
||||
} ?? [arg]
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns filtered compiler arguments from `xcodebuild` to something that SourceKit/Clang will accept.
|
||||
var filteringCompilerArguments: [String] {
|
||||
var args = self
|
||||
if args.first == "swiftc" {
|
||||
args.removeFirst()
|
||||
}
|
||||
|
||||
// https://github.com/realm/SwiftLint/issues/3365
|
||||
args = args.map { $0.replacingOccurrences(of: "\\=", with: "=") }
|
||||
args = args.map { $0.replacingOccurrences(of: "\\ ", with: " ") }
|
||||
args.append(contentsOf: ["-D", "DEBUG"])
|
||||
var shouldContinueToFilterArguments = true
|
||||
while shouldContinueToFilterArguments {
|
||||
(args, shouldContinueToFilterArguments) = partiallyFilter(arguments: args)
|
||||
}
|
||||
|
||||
return args.filter {
|
||||
![
|
||||
"-parseable-output",
|
||||
"-incremental",
|
||||
"-serialize-diagnostics",
|
||||
"-emit-dependencies",
|
||||
"-use-frontend-parseable-output"
|
||||
].contains($0)
|
||||
}.map {
|
||||
if $0 == "-O" {
|
||||
return "-Onone"
|
||||
} else if $0 == "-DNDEBUG=1" {
|
||||
return "-DDEBUG=1"
|
||||
}
|
||||
return $0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
#endif
|
||||
|
||||
enum ExitHelper {
|
||||
static func successfullyExit() {
|
||||
#if os(Linux)
|
||||
// Workaround for https://github.com/apple/swift/issues/59961
|
||||
Glibc.exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import ArgumentParser
|
||||
|
||||
enum LeniencyOptions: String, EnumerableFlag {
|
||||
case strict, lenient
|
||||
|
||||
static func help(for value: LeniencyOptions) -> ArgumentHelp? {
|
||||
switch value {
|
||||
case .strict:
|
||||
return "Upgrades warnings to serious violations (errors)."
|
||||
case .lenient:
|
||||
return "Downgrades serious violations to warnings, warning threshold is disabled."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Common Arguments
|
||||
|
||||
struct LintOrAnalyzeArguments: ParsableArguments {
|
||||
@Option(help: "The path to one or more SwiftLint configuration files, evaluated as a parent-child hierarchy.")
|
||||
var config = [String]()
|
||||
@Flag(name: [.long, .customLong("autocorrect")], help: "Correct violations whenever possible.")
|
||||
var fix = false
|
||||
@Flag(help: """
|
||||
Should reformat the Swift files using the same mechanism used by Xcode (via SourceKit).
|
||||
Only applied with `--fix`/`--autocorrect`.
|
||||
""")
|
||||
var format = false
|
||||
@Flag(help: "Use an alternative algorithm to exclude paths for `excluded`, which may be faster in some cases.")
|
||||
var useAlternativeExcluding = false
|
||||
@Flag(help: "Read SCRIPT_INPUT_FILE* environment variables as files.")
|
||||
var useScriptInputFiles = false
|
||||
@Flag(exclusivity: .exclusive)
|
||||
var leniency: LeniencyOptions?
|
||||
@Flag(help: "Exclude files in config `excluded` even if their paths are explicitly specified.")
|
||||
var forceExclude = false
|
||||
@Flag(help: "Save benchmarks to `benchmark_files.txt` and `benchmark_rules.txt`.")
|
||||
var benchmark = false
|
||||
@Option(help: "The reporter used to log errors and warnings.")
|
||||
var reporter: String?
|
||||
@Flag(help: "Use the in-process version of SourceKit.")
|
||||
var inProcessSourcekit = false
|
||||
@Option(help: "The file where violations should be saved. Prints to stdout by default.")
|
||||
var output: String?
|
||||
@Flag(help: "Show a live-updating progress bar instead of each file being processed.")
|
||||
var progress = false
|
||||
}
|
||||
|
||||
// MARK: - Common Argument Help
|
||||
|
||||
// It'd be great to be able to parameterize an `@OptionGroup` so we could move these options into
|
||||
// `LintOrAnalyzeArguments`.
|
||||
|
||||
func pathOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
ArgumentHelp(visibility: .hidden)
|
||||
}
|
||||
|
||||
func pathsArgumentDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
"List of paths to the files or directories to \(mode.imperative)."
|
||||
}
|
||||
|
||||
func quietOptionDescription(for mode: LintOrAnalyzeMode) -> ArgumentHelp {
|
||||
"Don't print status logs like '\(mode.verb.capitalized) <file>' & 'Done \(mode.verb)'."
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
import Dispatch
|
||||
import Foundation
|
||||
@_spi(TestHelper)
|
||||
import SwiftLintFramework
|
||||
|
||||
enum LintOrAnalyzeMode {
|
||||
case lint, analyze
|
||||
|
||||
var imperative: String {
|
||||
switch self {
|
||||
case .lint:
|
||||
return "lint"
|
||||
case .analyze:
|
||||
return "analyze"
|
||||
}
|
||||
}
|
||||
|
||||
var verb: String {
|
||||
switch self {
|
||||
case .lint:
|
||||
return "linting"
|
||||
case .analyze:
|
||||
return "analyzing"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintOrAnalyzeCommand {
|
||||
static func run(_ options: LintOrAnalyzeOptions) async throws {
|
||||
if options.inProcessSourcekit {
|
||||
queuedPrintError(
|
||||
"""
|
||||
warning: The --in-process-sourcekit option is deprecated. \
|
||||
SwiftLint now always uses an in-process SourceKit.
|
||||
"""
|
||||
)
|
||||
}
|
||||
try await Signposts.record(name: "LintOrAnalyzeCommand.run") {
|
||||
try await options.autocorrect ? autocorrect(options) : lintOrAnalyze(options)
|
||||
}
|
||||
ExitHelper.successfullyExit()
|
||||
}
|
||||
|
||||
private static func lintOrAnalyze(_ options: LintOrAnalyzeOptions) async throws {
|
||||
let builder = LintOrAnalyzeResultBuilder(options)
|
||||
let files = try await collectViolations(builder: builder)
|
||||
try Signposts.record(name: "LintOrAnalyzeCommand.PostProcessViolations") {
|
||||
try postProcessViolations(files: files, builder: builder)
|
||||
}
|
||||
}
|
||||
|
||||
private static func collectViolations(builder: LintOrAnalyzeResultBuilder) async throws -> [SwiftLintFile] {
|
||||
let options = builder.options
|
||||
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
|
||||
return try await builder.configuration.visitLintableFiles(options: options, cache: builder.cache,
|
||||
storage: builder.storage) { linter in
|
||||
let currentViolations: [StyleViolation]
|
||||
if options.benchmark {
|
||||
CustomRuleTimer.shared.activate()
|
||||
let start = Date()
|
||||
let (violationsBeforeLeniency, currentRuleTimes) = linter
|
||||
.styleViolationsAndRuleTimes(using: builder.storage)
|
||||
currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency)
|
||||
visitorMutationQueue.sync {
|
||||
builder.fileBenchmark.record(file: linter.file, from: start)
|
||||
currentRuleTimes.forEach { builder.ruleBenchmark.record(id: $0, time: $1) }
|
||||
builder.violations += currentViolations
|
||||
}
|
||||
} else {
|
||||
currentViolations = applyLeniency(options: options,
|
||||
violations: linter.styleViolations(using: builder.storage))
|
||||
visitorMutationQueue.sync {
|
||||
builder.violations += currentViolations
|
||||
}
|
||||
}
|
||||
linter.file.invalidateCache()
|
||||
builder.report(violations: currentViolations, realtimeCondition: true)
|
||||
}
|
||||
}
|
||||
|
||||
private static func postProcessViolations(files: [SwiftLintFile], builder: LintOrAnalyzeResultBuilder) throws {
|
||||
let options = builder.options
|
||||
let configuration = builder.configuration
|
||||
if isWarningThresholdBroken(configuration: configuration, violations: builder.violations)
|
||||
&& !options.lenient {
|
||||
builder.violations.append(
|
||||
createThresholdViolation(threshold: configuration.warningThreshold!)
|
||||
)
|
||||
builder.report(violations: [builder.violations.last!], realtimeCondition: true)
|
||||
}
|
||||
builder.report(violations: builder.violations, realtimeCondition: false)
|
||||
let numberOfSeriousViolations = builder.violations.filter({ $0.severity == .error }).count
|
||||
if !options.quiet {
|
||||
printStatus(violations: builder.violations, files: files, serious: numberOfSeriousViolations,
|
||||
verb: options.verb)
|
||||
}
|
||||
if options.benchmark {
|
||||
builder.fileBenchmark.save()
|
||||
for (id, time) in CustomRuleTimer.shared.dump() {
|
||||
builder.ruleBenchmark.record(id: id, time: time)
|
||||
}
|
||||
builder.ruleBenchmark.save()
|
||||
if !options.quiet, let memoryUsage = memoryUsage() {
|
||||
queuedPrintError(memoryUsage)
|
||||
}
|
||||
}
|
||||
try builder.cache?.save()
|
||||
guard numberOfSeriousViolations == 0 else { exit(2) }
|
||||
}
|
||||
|
||||
private static func printStatus(violations: [StyleViolation], files: [SwiftLintFile], serious: Int, verb: String) {
|
||||
let pluralSuffix = { (collection: [Any]) -> String in
|
||||
return collection.count != 1 ? "s" : ""
|
||||
}
|
||||
queuedPrintError(
|
||||
"Done \(verb)! Found \(violations.count) violation\(pluralSuffix(violations)), " +
|
||||
"\(serious) serious in \(files.count) file\(pluralSuffix(files))."
|
||||
)
|
||||
}
|
||||
|
||||
private static func isWarningThresholdBroken(configuration: Configuration,
|
||||
violations: [StyleViolation]) -> Bool {
|
||||
guard let warningThreshold = configuration.warningThreshold else { return false }
|
||||
let numberOfWarningViolations = violations.filter({ $0.severity == .warning }).count
|
||||
return numberOfWarningViolations >= warningThreshold
|
||||
}
|
||||
|
||||
private static func createThresholdViolation(threshold: Int) -> StyleViolation {
|
||||
let description = RuleDescription(
|
||||
identifier: "warning_threshold",
|
||||
name: "Warning Threshold",
|
||||
description: "Number of warnings thrown is above the threshold",
|
||||
kind: .lint
|
||||
)
|
||||
return StyleViolation(
|
||||
ruleDescription: description,
|
||||
severity: .error,
|
||||
location: Location(file: "", line: 0, character: 0),
|
||||
reason: "Number of warnings exceeded threshold of \(threshold).")
|
||||
}
|
||||
|
||||
private static func applyLeniency(options: LintOrAnalyzeOptions, violations: [StyleViolation]) -> [StyleViolation] {
|
||||
switch (options.lenient, options.strict) {
|
||||
case (false, false):
|
||||
return violations
|
||||
|
||||
case (true, false):
|
||||
return violations.map {
|
||||
if $0.severity == .error {
|
||||
return $0.with(severity: .warning)
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
case (false, true):
|
||||
return violations.map {
|
||||
if $0.severity == .warning {
|
||||
return $0.with(severity: .error)
|
||||
} else {
|
||||
return $0
|
||||
}
|
||||
}
|
||||
|
||||
case (true, true):
|
||||
queuedFatalError("Invalid command line options: 'lenient' and 'strict' are mutually exclusive.")
|
||||
}
|
||||
}
|
||||
|
||||
private static func autocorrect(_ options: LintOrAnalyzeOptions) async throws {
|
||||
let storage = RuleStorage()
|
||||
let configuration = Configuration(options: options)
|
||||
let correctionsBuilder = CorrectionsBuilder()
|
||||
let files = try await configuration
|
||||
.visitLintableFiles(options: options, cache: nil, storage: storage) { linter in
|
||||
if options.format {
|
||||
switch configuration.indentation {
|
||||
case .tabs:
|
||||
linter.format(useTabs: true, indentWidth: 4)
|
||||
case .spaces(let count):
|
||||
linter.format(useTabs: false, indentWidth: count)
|
||||
}
|
||||
}
|
||||
|
||||
let corrections = linter.correct(using: storage)
|
||||
if !corrections.isEmpty && !options.quiet {
|
||||
if options.useSTDIN {
|
||||
queuedPrint(linter.file.contents)
|
||||
} else {
|
||||
if options.progress {
|
||||
await correctionsBuilder.append(corrections)
|
||||
} else {
|
||||
let correctionLogs = corrections.map(\.consoleDescription)
|
||||
queuedPrint(correctionLogs.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !options.quiet {
|
||||
if options.progress {
|
||||
let corrections = await correctionsBuilder.corrections
|
||||
if !corrections.isEmpty {
|
||||
let correctionLogs = corrections.map(\.consoleDescription)
|
||||
options.writeToOutput(correctionLogs.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
let pluralSuffix = { (collection: [Any]) -> String in
|
||||
return collection.count != 1 ? "s" : ""
|
||||
}
|
||||
queuedPrintError("Done correcting \(files.count) file\(pluralSuffix(files))!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintOrAnalyzeOptions {
|
||||
let mode: LintOrAnalyzeMode
|
||||
let paths: [String]
|
||||
let useSTDIN: Bool
|
||||
let configurationFiles: [String]
|
||||
let strict: Bool
|
||||
let lenient: Bool
|
||||
let forceExclude: Bool
|
||||
let useExcludingByPrefix: Bool
|
||||
let useScriptInputFiles: Bool
|
||||
let benchmark: Bool
|
||||
let reporter: String?
|
||||
let quiet: Bool
|
||||
let output: String?
|
||||
let progress: Bool
|
||||
let cachePath: String?
|
||||
let ignoreCache: Bool
|
||||
let enableAllRules: Bool
|
||||
let autocorrect: Bool
|
||||
let format: Bool
|
||||
let compilerLogPath: String?
|
||||
let compileCommands: String?
|
||||
let inProcessSourcekit: Bool
|
||||
|
||||
var verb: String {
|
||||
if autocorrect {
|
||||
return "correcting"
|
||||
} else {
|
||||
return mode.verb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LintOrAnalyzeResultBuilder {
|
||||
var fileBenchmark = Benchmark(name: "files")
|
||||
var ruleBenchmark = Benchmark(name: "rules")
|
||||
var violations = [StyleViolation]()
|
||||
let storage = RuleStorage()
|
||||
let configuration: Configuration
|
||||
let reporter: Reporter.Type
|
||||
let cache: LinterCache?
|
||||
let options: LintOrAnalyzeOptions
|
||||
|
||||
init(_ options: LintOrAnalyzeOptions) {
|
||||
let config = Signposts.record(name: "LintOrAnalyzeCommand.ParseConfiguration") {
|
||||
Configuration(options: options)
|
||||
}
|
||||
configuration = config
|
||||
reporter = reporterFrom(identifier: options.reporter ?? config.reporter)
|
||||
if options.ignoreCache || ProcessInfo.processInfo.isLikelyXcodeCloudEnvironment {
|
||||
cache = nil
|
||||
} else {
|
||||
cache = LinterCache(configuration: config)
|
||||
}
|
||||
self.options = options
|
||||
|
||||
if let outFile = options.output {
|
||||
do {
|
||||
try Data().write(to: URL(fileURLWithPath: outFile))
|
||||
} catch {
|
||||
queuedPrintError("Could not write to file at path \(outFile)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func report(violations: [StyleViolation], realtimeCondition: Bool) {
|
||||
if (reporter.isRealtime && (!options.progress || options.output != nil)) == realtimeCondition {
|
||||
let report = reporter.generateReport(violations)
|
||||
if !report.isEmpty {
|
||||
options.writeToOutput(report)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension LintOrAnalyzeOptions {
|
||||
func writeToOutput(_ string: String) {
|
||||
guard let outFile = output else {
|
||||
queuedPrint(string)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let outFileURL = URL(fileURLWithPath: outFile)
|
||||
let fileUpdater = try FileHandle(forUpdating: outFileURL)
|
||||
fileUpdater.seekToEndOfFile()
|
||||
fileUpdater.write(Data((string + "\n").utf8))
|
||||
fileUpdater.closeFile()
|
||||
} catch {
|
||||
queuedPrintError("Could not write to file at path \(outFile)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private actor CorrectionsBuilder {
|
||||
private(set) var corrections: [Correction] = []
|
||||
|
||||
func append(_ corrections: [Correction]) {
|
||||
self.corrections.append(contentsOf: corrections)
|
||||
}
|
||||
}
|
||||
|
||||
private func memoryUsage() -> String? {
|
||||
#if os(Linux)
|
||||
return nil
|
||||
#else
|
||||
var info = mach_task_basic_info()
|
||||
let basicInfoCount = MemoryLayout<mach_task_basic_info>.stride / MemoryLayout<natural_t>.stride
|
||||
var count = mach_msg_type_number_t(basicInfoCount)
|
||||
|
||||
let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: basicInfoCount) {
|
||||
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
|
||||
}
|
||||
}
|
||||
|
||||
if kerr == KERN_SUCCESS {
|
||||
let bytes = Measurement<UnitInformationStorage>(value: Double(info.resident_size), unit: .bytes)
|
||||
let formatted = ByteCountFormatter().string(from: bytes)
|
||||
return "Memory used: \(formatted)"
|
||||
} else {
|
||||
let errorMessage = String(cString: mach_error_string(kerr), encoding: .ascii)
|
||||
return "Error with task_info(): \(errorMessage ?? "unknown")"
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftLintFramework
|
||||
|
||||
typealias File = String
|
||||
typealias Arguments = [String]
|
||||
|
||||
class CompilerInvocations {
|
||||
static func buildLog(compilerInvocations: [[String]]) -> CompilerInvocations {
|
||||
return ArrayCompilerInvocations(invocations: compilerInvocations)
|
||||
}
|
||||
|
||||
static func compilationDatabase(compileCommands: [File: Arguments]) -> CompilerInvocations {
|
||||
return CompilationDatabaseInvocations(compileCommands: compileCommands)
|
||||
}
|
||||
|
||||
/// Default implementation
|
||||
func arguments(forFile path: String?) -> Arguments { [] }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private class ArrayCompilerInvocations: CompilerInvocations {
|
||||
private let invocationsByArgument: [String: [Arguments]]
|
||||
|
||||
init(invocations: [Arguments]) {
|
||||
// Store invocations by the path, so next when we'll be asked for arguments,
|
||||
// we'll be able to return them faster
|
||||
self.invocationsByArgument = invocations.reduce(into: [:]) { result, arguments in
|
||||
arguments.forEach { result[$0, default: []].append(arguments) }
|
||||
}
|
||||
}
|
||||
|
||||
override func arguments(forFile path: String?) -> Arguments {
|
||||
return path.flatMap { path in
|
||||
return invocationsByArgument[path]?.first
|
||||
} ?? []
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationDatabaseInvocations: CompilerInvocations {
|
||||
private let compileCommands: [File: Arguments]
|
||||
|
||||
init(compileCommands: [File: Arguments]) {
|
||||
self.compileCommands = compileCommands
|
||||
}
|
||||
|
||||
override func arguments(forFile path: String?) -> Arguments {
|
||||
return path.flatMap { path in
|
||||
return compileCommands[path] ??
|
||||
compileCommands[path.path(relativeTo: FileManager.default.currentDirectoryPath)]
|
||||
} ?? []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LintOrAnalyzeModeWithCompilerArguments {
|
||||
case lint
|
||||
case analyze(allCompilerInvocations: CompilerInvocations)
|
||||
}
|
||||
|
||||
private func resolveParamsFiles(args: [String]) -> [String] {
|
||||
return args.reduce(into: []) { (allArgs: inout [String], arg: String) -> Void in
|
||||
if arg.hasPrefix("@"), let contents = try? String(contentsOfFile: String(arg.dropFirst())) {
|
||||
allArgs.append(contentsOf: resolveParamsFiles(args: contents.split(separator: "\n").map(String.init)))
|
||||
} else {
|
||||
allArgs.append(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LintableFilesVisitor {
|
||||
let paths: [String]
|
||||
let action: String
|
||||
let useSTDIN: Bool
|
||||
let quiet: Bool
|
||||
let showProgressBar: Bool
|
||||
let useScriptInputFiles: Bool
|
||||
let forceExclude: Bool
|
||||
let useExcludingByPrefix: Bool
|
||||
let cache: LinterCache?
|
||||
let parallel: Bool
|
||||
let allowZeroLintableFiles: Bool
|
||||
let mode: LintOrAnalyzeModeWithCompilerArguments
|
||||
let block: (CollectedLinter) async -> Void
|
||||
|
||||
private init(paths: [String], action: String, useSTDIN: Bool, quiet: Bool, showProgressBar: Bool,
|
||||
useScriptInputFiles: Bool, forceExclude: Bool, useExcludingByPrefix: Bool,
|
||||
cache: LinterCache?, compilerInvocations: CompilerInvocations?,
|
||||
allowZeroLintableFiles: Bool, block: @escaping (CollectedLinter) async -> Void) {
|
||||
self.paths = resolveParamsFiles(args: paths)
|
||||
self.action = action
|
||||
self.useSTDIN = useSTDIN
|
||||
self.quiet = quiet
|
||||
self.showProgressBar = showProgressBar
|
||||
self.useScriptInputFiles = useScriptInputFiles
|
||||
self.forceExclude = forceExclude
|
||||
self.useExcludingByPrefix = useExcludingByPrefix
|
||||
self.cache = cache
|
||||
if let compilerInvocations {
|
||||
self.mode = .analyze(allCompilerInvocations: compilerInvocations)
|
||||
// SourceKit had some changes in 5.6 that makes it ~100x more expensive
|
||||
// to process files concurrently. By processing files serially, it's
|
||||
// only 2x slower than before.
|
||||
self.parallel = SwiftVersion.current < .fiveDotSix
|
||||
} else {
|
||||
self.mode = .lint
|
||||
self.parallel = true
|
||||
}
|
||||
self.block = block
|
||||
self.allowZeroLintableFiles = allowZeroLintableFiles
|
||||
}
|
||||
|
||||
static func create(_ options: LintOrAnalyzeOptions,
|
||||
cache: LinterCache?,
|
||||
allowZeroLintableFiles: Bool,
|
||||
block: @escaping (CollectedLinter) async -> Void)
|
||||
throws -> LintableFilesVisitor {
|
||||
try Signposts.record(name: "LintableFilesVisitor.Create") {
|
||||
let compilerInvocations: CompilerInvocations?
|
||||
if options.mode == .lint {
|
||||
compilerInvocations = nil
|
||||
} else {
|
||||
compilerInvocations = try loadCompilerInvocations(options)
|
||||
}
|
||||
|
||||
return LintableFilesVisitor(
|
||||
paths: options.paths, action: options.verb.bridge().capitalized,
|
||||
useSTDIN: options.useSTDIN, quiet: options.quiet,
|
||||
showProgressBar: options.progress,
|
||||
useScriptInputFiles: options.useScriptInputFiles,
|
||||
forceExclude: options.forceExclude,
|
||||
useExcludingByPrefix: options.useExcludingByPrefix,
|
||||
cache: cache,
|
||||
compilerInvocations: compilerInvocations,
|
||||
allowZeroLintableFiles: allowZeroLintableFiles, block: block
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func shouldSkipFile(atPath path: String?) -> Bool {
|
||||
switch self.mode {
|
||||
case .lint:
|
||||
return false
|
||||
case let .analyze(compilerInvocations):
|
||||
let compilerArguments = compilerInvocations.arguments(forFile: path)
|
||||
return compilerArguments.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
func linter(forFile file: SwiftLintFile, configuration: Configuration) -> Linter {
|
||||
switch self.mode {
|
||||
case .lint:
|
||||
return Linter(file: file, configuration: configuration, cache: cache)
|
||||
case let .analyze(compilerInvocations):
|
||||
let compilerArguments = compilerInvocations.arguments(forFile: file.path)
|
||||
return Linter(file: file, configuration: configuration, compilerArguments: compilerArguments)
|
||||
}
|
||||
}
|
||||
|
||||
private static func loadCompilerInvocations(_ options: LintOrAnalyzeOptions) throws -> CompilerInvocations {
|
||||
if let path = options.compilerLogPath {
|
||||
guard let compilerInvocations = self.loadLogCompilerInvocations(path) else {
|
||||
throw SwiftLintError.usageError(description: "Could not read compiler log at path: '\(path)'")
|
||||
}
|
||||
|
||||
return .buildLog(compilerInvocations: compilerInvocations)
|
||||
} else if let path = options.compileCommands {
|
||||
do {
|
||||
return .compilationDatabase(compileCommands: try self.loadCompileCommands(path))
|
||||
} catch {
|
||||
throw SwiftLintError.usageError(
|
||||
description: "Could not read compilation database at path: '\(path)' \(error.localizedDescription)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
throw SwiftLintError.usageError(description: "Could not read compiler invocations")
|
||||
}
|
||||
|
||||
private static func loadLogCompilerInvocations(_ path: String) -> [[String]]? {
|
||||
if let data = FileManager.default.contents(atPath: path),
|
||||
let logContents = String(data: data, encoding: .utf8) {
|
||||
if logContents.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CompilerArgumentsExtractor.allCompilerInvocations(compilerLogs: logContents)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func loadCompileCommands(_ path: String) throws -> [File: Arguments] {
|
||||
guard let fileContents = FileManager.default.contents(atPath: path) else {
|
||||
throw CompileCommandsLoadError.nonExistentFile(path)
|
||||
}
|
||||
|
||||
if path.hasSuffix(".yaml") || path.hasSuffix(".yml") {
|
||||
// Assume this is a SwiftPM yaml file
|
||||
return try SwiftPMCompilationDB.parse(yaml: fileContents)
|
||||
}
|
||||
|
||||
guard let object = try? JSONSerialization.jsonObject(with: fileContents),
|
||||
let compileDB = object as? [[String: Any]] else {
|
||||
throw CompileCommandsLoadError.malformedCommands(path)
|
||||
}
|
||||
|
||||
// Convert the compilation database to a dictionary, with source files as keys and compiler arguments as values.
|
||||
//
|
||||
// Compilation databases are an array of dictionaries. Each dict has "file" and "arguments" keys.
|
||||
var commands = [File: Arguments]()
|
||||
for (index, entry) in compileDB.enumerated() {
|
||||
guard let file = entry["file"] as? String else {
|
||||
throw CompileCommandsLoadError.malformedFile(path, index)
|
||||
}
|
||||
|
||||
guard let arguments = entry["arguments"] as? [String] else {
|
||||
throw CompileCommandsLoadError.malformedArguments(path, index)
|
||||
}
|
||||
|
||||
guard arguments.contains(file) else {
|
||||
throw CompileCommandsLoadError.missingFileInArguments(path, index, arguments)
|
||||
}
|
||||
|
||||
commands[file] = arguments.filteringCompilerArguments
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
|
||||
private enum CompileCommandsLoadError: LocalizedError {
|
||||
case nonExistentFile(String)
|
||||
case malformedCommands(String)
|
||||
case malformedFile(String, Int)
|
||||
case malformedArguments(String, Int)
|
||||
case missingFileInArguments(String, Int, [String])
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case let .nonExistentFile(path):
|
||||
return "Could not read compile commands file at '\(path)'"
|
||||
case let .malformedCommands(path):
|
||||
return "Compile commands file at '\(path)' isn't in the correct format"
|
||||
case let .malformedFile(path, index):
|
||||
return "Missing or invalid (must be a string) 'file' key in \(path) at index \(index)"
|
||||
case let .malformedArguments(path, index):
|
||||
return "Missing or invalid (must be an array of strings) 'arguments' key in \(path) at index \(index)"
|
||||
case let .missingFileInArguments(path, index, arguments):
|
||||
return "Entry in \(path) at index \(index) has 'arguments' which do not contain the 'file': \(arguments)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import Dispatch
|
||||
import Foundation
|
||||
import SwiftLintFramework
|
||||
|
||||
// Inspired by https://github.com/jkandzi/Progress.swift
|
||||
actor ProgressBar {
|
||||
private var index = 1
|
||||
private var lastPrintedTime: TimeInterval = 0.0
|
||||
private let startTime = uptime()
|
||||
private let count: Int
|
||||
|
||||
init(count: Int) {
|
||||
self.count = count
|
||||
}
|
||||
|
||||
func initialize() {
|
||||
// When progress is printed, the previous line is reset, so print an empty line before anything else
|
||||
queuedPrintError("")
|
||||
}
|
||||
|
||||
func printNext() {
|
||||
guard index <= count else { return }
|
||||
|
||||
let currentTime = uptime()
|
||||
if currentTime - lastPrintedTime > 0.1 || index == count {
|
||||
let lineReset = "\u{1B}[1A\u{1B}[K"
|
||||
let bar = makeBar()
|
||||
let timeEstimate = makeTimeEstimate(currentTime: currentTime)
|
||||
let lineContents = "\(index) of \(count) \(bar) \(timeEstimate)"
|
||||
queuedPrintError("\(lineReset)\(lineContents)")
|
||||
lastPrintedTime = currentTime
|
||||
}
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func makeBar() -> String {
|
||||
let barLength = 30
|
||||
let completedBarElements = Int(Double(barLength) * (Double(index) / Double(count)))
|
||||
let barArray = Array(repeating: "=", count: completedBarElements) +
|
||||
Array(repeating: " ", count: barLength - completedBarElements)
|
||||
return "[\(barArray.joined())]"
|
||||
}
|
||||
|
||||
private func makeTimeEstimate(currentTime: TimeInterval) -> String {
|
||||
let totalTime = currentTime - startTime
|
||||
let itemsPerSecond = Double(index) / totalTime
|
||||
let estimatedTimeRemaining = Double(count - index) / itemsPerSecond
|
||||
let estimatedTimeRemainingString = "\(Int(estimatedTimeRemaining))s"
|
||||
return "ETA: \(estimatedTimeRemainingString) (\(Int(itemsPerSecond)) files/s)"
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
// swiftlint:disable:next identifier_name
|
||||
private let NSEC_PER_SEC = 1_000_000_000
|
||||
#endif
|
||||
|
||||
private func uptime() -> TimeInterval {
|
||||
Double(DispatchTime.now().uptimeNanoseconds) / Double(NSEC_PER_SEC)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@_spi(TestHelper)
|
||||
import SwiftLintFramework
|
||||
|
||||
extension RulesFilter {
|
||||
struct ExcludingOptions: OptionSet {
|
||||
let rawValue: Int
|
||||
|
||||
static let enabled = Self(rawValue: 1 << 0)
|
||||
static let disabled = Self(rawValue: 1 << 1)
|
||||
static let uncorrectable = Self(rawValue: 1 << 2)
|
||||
}
|
||||
}
|
||||
|
||||
class RulesFilter {
|
||||
private let allRules: RuleList
|
||||
private let enabledRules: [Rule]
|
||||
|
||||
init(allRules: RuleList = primaryRuleList, enabledRules: [Rule]) {
|
||||
self.allRules = allRules
|
||||
self.enabledRules = enabledRules
|
||||
}
|
||||
|
||||
func getRules(excluding excludingOptions: ExcludingOptions) -> RuleList {
|
||||
if excludingOptions.isEmpty {
|
||||
return allRules
|
||||
}
|
||||
|
||||
let filtered: [Rule.Type] = allRules.list.compactMap { ruleID, ruleType in
|
||||
let enabledRule = enabledRules.first { rule in
|
||||
type(of: rule).description.identifier == ruleID
|
||||
}
|
||||
let isRuleEnabled = enabledRule != nil
|
||||
|
||||
if excludingOptions.contains(.enabled) && isRuleEnabled {
|
||||
return nil
|
||||
}
|
||||
if excludingOptions.contains(.disabled) && !isRuleEnabled {
|
||||
return nil
|
||||
}
|
||||
if excludingOptions.contains(.uncorrectable) && !(ruleType is CorrectableRule.Type) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ruleType
|
||||
}
|
||||
|
||||
return RuleList(rules: filtered)
|
||||
}
|
||||
}
|
||||
73
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Signposts.swift
Normal file
73
SideStoreApp/Sources/Cargo/swiftlint/Helpers/Signposts.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
#if canImport(os)
|
||||
import os.signpost
|
||||
private let timelineLog = OSLog(subsystem: "io.realm.swiftlint", category: "Timeline")
|
||||
private let fileLog = OSLog(subsystem: "io.realm.swiftlint", category: "File")
|
||||
#endif
|
||||
|
||||
struct Signposts {
|
||||
enum Span {
|
||||
case timeline, file(String)
|
||||
}
|
||||
|
||||
static func record<R>(name: StaticString, span: Span = .timeline, body: () throws -> R) rethrows -> R {
|
||||
#if canImport(os)
|
||||
let log: OSLog
|
||||
let description: String?
|
||||
switch span {
|
||||
case .timeline:
|
||||
log = timelineLog
|
||||
description = nil
|
||||
case .file(let file):
|
||||
log = fileLog
|
||||
description = file
|
||||
}
|
||||
let signpostID = OSSignpostID(log: log)
|
||||
if let description {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
|
||||
let result = try body()
|
||||
if let description {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
return result
|
||||
#else
|
||||
return try body()
|
||||
#endif
|
||||
}
|
||||
|
||||
static func record<R>(name: StaticString, span: Span = .timeline, body: () async throws -> R) async rethrows -> R {
|
||||
#if canImport(os)
|
||||
let log: OSLog
|
||||
let description: String?
|
||||
switch span {
|
||||
case .timeline:
|
||||
log = timelineLog
|
||||
description = nil
|
||||
case .file(let file):
|
||||
log = fileLog
|
||||
description = file
|
||||
}
|
||||
let signpostID = OSSignpostID(log: log)
|
||||
if let description {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.begin, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
|
||||
let result = try await body()
|
||||
if let description {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID, "%{public}s", description)
|
||||
} else {
|
||||
os_signpost(.end, log: log, name: name, signpostID: signpostID)
|
||||
}
|
||||
return result
|
||||
#else
|
||||
return try await body()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
enum SwiftLintError: LocalizedError {
|
||||
case usageError(description: String)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .usageError(let description):
|
||||
return description
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
private struct SwiftPMCommand: Codable {
|
||||
let tool: String
|
||||
let module: String?
|
||||
let sources: [String]?
|
||||
let args: [String]?
|
||||
let importPaths: [String]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case tool
|
||||
case module = "module-name"
|
||||
case sources
|
||||
case args = "other-args"
|
||||
case importPaths = "import-paths"
|
||||
}
|
||||
}
|
||||
|
||||
private struct SwiftPMNode: Codable {}
|
||||
|
||||
private struct SwiftPMNodes: Codable {
|
||||
let nodes: [String: SwiftPMNode]
|
||||
}
|
||||
|
||||
struct SwiftPMCompilationDB: Codable {
|
||||
private let commands: [String: SwiftPMCommand]
|
||||
|
||||
static func parse(yaml: Data) throws -> [File: Arguments] {
|
||||
let decoder = YAMLDecoder()
|
||||
let compilationDB: SwiftPMCompilationDB
|
||||
|
||||
if ProcessInfo.processInfo.environment["TEST_SRCDIR"] != nil {
|
||||
// Running tests
|
||||
let nodes = try decoder.decode(SwiftPMNodes.self, from: yaml)
|
||||
let suffix = "/Source/swiftlint/"
|
||||
let pathToReplace = Array(nodes.nodes.keys.filter({ node in
|
||||
node.hasSuffix(suffix)
|
||||
}))[0].dropLast(suffix.count - 1)
|
||||
let stringFileContents = String(data: yaml, encoding: .utf8)!
|
||||
.replacingOccurrences(of: pathToReplace, with: "")
|
||||
compilationDB = try decoder.decode(Self.self, from: stringFileContents)
|
||||
} else {
|
||||
compilationDB = try decoder.decode(Self.self, from: yaml)
|
||||
}
|
||||
|
||||
let swiftCompilerCommands = compilationDB.commands
|
||||
.filter { $0.value.tool == "swift-compiler" }
|
||||
let allSwiftSources = swiftCompilerCommands
|
||||
.flatMap { $0.value.sources ?? [] }
|
||||
.filter { $0.hasSuffix(".swift") }
|
||||
return Dictionary(uniqueKeysWithValues: allSwiftSources.map { swiftSource in
|
||||
let command = swiftCompilerCommands
|
||||
.values
|
||||
.first { $0.sources?.contains(swiftSource) == true }
|
||||
|
||||
guard let command,
|
||||
let module = command.module,
|
||||
let sources = command.sources,
|
||||
let arguments = command.args,
|
||||
let importPaths = command.importPaths
|
||||
else {
|
||||
return (swiftSource, [])
|
||||
}
|
||||
|
||||
let args = ["-module-name", module] +
|
||||
sources +
|
||||
arguments.filteringCompilerArguments +
|
||||
["-I"] + importPaths
|
||||
|
||||
return (swiftSource, args)
|
||||
})
|
||||
}
|
||||
}
|
||||
59
SideStoreApp/Sources/Cargo/xcframework/BuildSetting.swift
Normal file
59
SideStoreApp/Sources/Cargo/xcframework/BuildSetting.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// BuildSettings.swift
|
||||
// Cargo
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
//set -eu;
|
||||
//
|
||||
//BUILT_SRC="./em_proxy/$LIB_FILE_NAME.a"
|
||||
//ln -f -- "$BUILT_SRC" "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" || cp "$BUILT_SRC" "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"
|
||||
//echo "$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH"
|
||||
|
||||
//# generated with cargo-xcode 1.5.0
|
||||
//# modified to use prebuilt binaries
|
||||
//
|
||||
//set -eu;
|
||||
//
|
||||
//BUILT_SRC="./minimuxer/$LIB_FILE_NAME.a"
|
||||
//ln -f -- "$BUILT_SRC" "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" || cp "$BUILT_SRC" "$TARGET_BUILD_DIR/$EXECUTABLE_PATH"
|
||||
//echo "$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH"
|
||||
//
|
||||
//# xcode generates dep file, but for its own path, so append our rename to it
|
||||
// #DEP_FILE_SRC="minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}"
|
||||
// #if [ -f "$DEP_FILE_SRC" ]; then
|
||||
//# DEP_FILE_DST="${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d"
|
||||
//# cp -f "$DEP_FILE_SRC" "$DEP_FILE_DST"
|
||||
//# echo >> "$DEP_FILE_DST" "$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC"
|
||||
//#fi
|
||||
//
|
||||
//# lipo script needs to know all the platform-specific files that have been built
|
||||
//# archs is in the file name, so that paths don't stay around after archs change
|
||||
//# must match input for LipoScript
|
||||
// #FILE_LIST="${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist"
|
||||
// #touch "$FILE_LIST"
|
||||
// #if ! egrep -q "$SCRIPT_OUTPUT_FILE_0" "$FILE_LIST" ; then
|
||||
//# echo >> "$FILE_LIST" "$SCRIPT_OUTPUT_FILE_0"
|
||||
//#fi
|
||||
|
||||
|
||||
import ArgumentParser
|
||||
|
||||
/// A representation of a build setting in an Xcode project, e.g.
|
||||
/// `IPHONEOS_DEPLOYMENT_TARGET=13.0`
|
||||
struct BuildSetting: ExpressibleByArgument {
|
||||
/// The name of the build setting, e.g. `IPHONEOS_DEPLOYMENT_TARGET`
|
||||
let name: String
|
||||
/// The value of the build setting
|
||||
let value: String
|
||||
|
||||
init?(argument: String) {
|
||||
let components = argument.components(separatedBy: "=")
|
||||
guard components.count == 2 else { return nil }
|
||||
name = components[0].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
value = components[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
71
SideStoreApp/Sources/Cargo/xcframework/Command+Options.swift
Normal file
71
SideStoreApp/Sources/Cargo/xcframework/Command+Options.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Command+Options.swift
|
||||
// Cargo
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
import ArgumentParser
|
||||
import PackageModel
|
||||
|
||||
extension Command {
|
||||
struct Options: ParsableArguments {
|
||||
// MARK: - Package Loading
|
||||
|
||||
@Option(help: ArgumentHelp("The location of the Package", valueName: "directory"))
|
||||
var packagePath = "."
|
||||
|
||||
// MARK: - Building
|
||||
|
||||
@Option(help: ArgumentHelp("The location of the build/cache directory to use", valueName: "directory"))
|
||||
var buildPath = ".build"
|
||||
|
||||
@Option(help: ArgumentHelp("Build with a specific configuration", valueName: "debug|release"))
|
||||
var configuration = PackageModel.BuildConfiguration.release
|
||||
|
||||
@Flag(inversion: .prefixedNo, help: "Whether to clean before we build")
|
||||
var clean = true
|
||||
|
||||
@Flag(inversion: .prefixedNo, help: "Whether to include debug symbols in the built XCFramework")
|
||||
var debugSymbols = true
|
||||
|
||||
@Flag(help: "Prints the available products and targets")
|
||||
var listProducts = false
|
||||
|
||||
@Option(help: "The path to a .xcconfig file that can be used to override Xcode build settings. Relative to the package path.")
|
||||
var xcconfig: String?
|
||||
|
||||
@Flag(help: "Enables Library Evolution for the whole build stack. Normally we apply it only to the targets listed to be built to work around issues with projects that don't support it.")
|
||||
var stackEvolution: Bool = false
|
||||
|
||||
@Option(help: ArgumentHelp("Arbitrary Xcode build settings that are passed directly to the `xcodebuild` invocation. Can be specified multiple times.", valueName: "NAME=VALUE"))
|
||||
var xcSetting: [BuildSetting] = []
|
||||
|
||||
// MARK: - Output Options
|
||||
|
||||
@Option(
|
||||
help: ArgumentHelp(
|
||||
"A list of platforms you want to build for. Can be specified multiple times."
|
||||
+ " Default is to build for all platforms supported in your Package.swift, or all Apple platforms (except for maccatalyst platform) if omitted",
|
||||
valueName: TargetPlatform.allCases.map { $0.rawValue }.joined(separator: "|")
|
||||
)
|
||||
)
|
||||
var platform: [TargetPlatform] = []
|
||||
|
||||
@Option(help: ArgumentHelp("Where to place the compiled library", valueName: "directory"))
|
||||
var output = "."
|
||||
|
||||
@Flag(help: .hidden)
|
||||
var githubAction: Bool = false
|
||||
|
||||
// MARK: - Targets
|
||||
|
||||
@Argument(help: "An optional list of products (or targets) to build. Defaults to building all `.library` products")
|
||||
var products: [String] = []
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ParsableArguments Extensions
|
||||
|
||||
extension PackageModel.BuildConfiguration: ExpressibleByArgument {}
|
||||
142
SideStoreApp/Sources/Cargo/xcframework/Command.swift
Normal file
142
SideStoreApp/Sources/Cargo/xcframework/Command.swift
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Command.swift
|
||||
// Cargo
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import PackageLoading
|
||||
import PackageModel
|
||||
import TSCBasic
|
||||
import Workspace
|
||||
import Xcodeproj
|
||||
|
||||
struct Command: ParsableCommand {
|
||||
// MARK: - Configuration
|
||||
|
||||
static var configuration = CommandConfiguration(
|
||||
abstract: "Builds a `rust` package using `cargo`.",
|
||||
discussion:
|
||||
"""
|
||||
|
||||
""",
|
||||
version: "1.1.0"
|
||||
)
|
||||
|
||||
// MARK: - Arguments
|
||||
|
||||
@OptionGroup()
|
||||
var options: Options
|
||||
|
||||
// MARK: - Execution
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func run() throws {
|
||||
// load all/validate of the package info
|
||||
let package = try PackageInfo(options: options)
|
||||
|
||||
// validate that package to make sure we can generate it
|
||||
let validation = package.validationErrors()
|
||||
if validation.isEmpty == false {
|
||||
for error in validation {
|
||||
print(error.isFatal ? "Error:" : "Warning:", error.errorDescription!)
|
||||
}
|
||||
if validation.contains(where: { $0.isFatal }) {
|
||||
Darwin.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// generate the Xcode project file
|
||||
let generator = ProjectGenerator(package: package)
|
||||
|
||||
let platforms = try package.supportedPlatforms()
|
||||
|
||||
// get what we're building
|
||||
try generator.writeDistributionXcconfig()
|
||||
let project = try generator.generate()
|
||||
|
||||
// printing packages?
|
||||
if options.listProducts {
|
||||
package.printAllProducts(project: project)
|
||||
Darwin.exit(0)
|
||||
}
|
||||
|
||||
// get valid packages and their SDKs
|
||||
let productNames = try package.validProductNames(project: project)
|
||||
let sdks = platforms.flatMap { $0.sdks }
|
||||
|
||||
// we've applied the xcconfig to everything, but some dependencies (*cough* swift-nio)
|
||||
// have build errors, so we remove it from targets we're not building
|
||||
if options.stackEvolution == false {
|
||||
try project.enableDistribution(targets: productNames, xcconfig: AbsolutePath(package.distributionBuildXcconfig.path).relative(to: AbsolutePath(package.rootDirectory.path)))
|
||||
}
|
||||
|
||||
// save the project
|
||||
try project.save(to: generator.projectPath)
|
||||
|
||||
// start building
|
||||
let builder = XcodeBuilder(project: project, projectPath: generator.projectPath, package: package, options: options)
|
||||
|
||||
// clean first
|
||||
if options.clean {
|
||||
try builder.clean()
|
||||
}
|
||||
|
||||
// all of our targets for each platform, then group the resulting .frameworks by target
|
||||
var frameworkFiles: [String: [XcodeBuilder.BuildResult]] = [:]
|
||||
|
||||
for sdk in sdks {
|
||||
try builder.build(targets: productNames, sdk: sdk)
|
||||
.forEach { pair in
|
||||
if frameworkFiles[pair.key] == nil {
|
||||
frameworkFiles[pair.key] = []
|
||||
}
|
||||
frameworkFiles[pair.key]?.append(pair.value)
|
||||
}
|
||||
}
|
||||
|
||||
var xcframeworkFiles: [(String, Foundation.URL)] = []
|
||||
|
||||
// then we merge the resulting frameworks
|
||||
try frameworkFiles
|
||||
.forEach { pair in
|
||||
xcframeworkFiles.append((pair.key, try builder.merge(target: pair.key, buildResults: pair.value)))
|
||||
}
|
||||
|
||||
// zip it up if thats what they want
|
||||
if options.zip {
|
||||
let zipper = Zipper(package: package)
|
||||
let zipped = try xcframeworkFiles
|
||||
.flatMap { pair -> [Foundation.URL] in
|
||||
let zip = try zipper.zip(target: pair.0, version: self.options.zipVersion, file: pair.1)
|
||||
let checksum = try zipper.checksum(file: zip)
|
||||
try zipper.clean(file: pair.1)
|
||||
|
||||
return [zip, checksum]
|
||||
}
|
||||
|
||||
// notify the action if we have one
|
||||
if options.githubAction {
|
||||
let zips = zipped.map { $0.path }.joined(separator: "\n")
|
||||
let data = Data(zips.utf8)
|
||||
let url = Foundation.URL(fileURLWithPath: options.buildPath).appendingPathComponent("xcframework-zipfile.url")
|
||||
try data.write(to: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Errors
|
||||
|
||||
private enum Error: Swift.Error, LocalizedError {
|
||||
case noProducts
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .noProducts: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// Collection-Extensions.swift
|
||||
// swift-create-xcframework
|
||||
//
|
||||
// Created by Rob Amos on 9/5/20.
|
||||
//
|
||||
|
||||
extension Collection {
|
||||
var nonEmpty: Self? {
|
||||
isEmpty ? nil : self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// PackageDescription+Extensions.swift
|
||||
// swift-create-xcframework
|
||||
//
|
||||
// Created by Rob Amos on 7/5/20.
|
||||
//
|
||||
|
||||
import PackageModel
|
||||
|
||||
extension ProductType {
|
||||
var isLibrary: Bool {
|
||||
if case .library = self {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension Manifest {
|
||||
var libraryProductNames: [String] {
|
||||
products
|
||||
.compactMap { product in
|
||||
guard product.type.isLibrary else { return nil }
|
||||
return product.name
|
||||
}
|
||||
}
|
||||
}
|
||||
113
SideStoreApp/Sources/Cargo/xcframework/Platforms.swift
Normal file
113
SideStoreApp/Sources/Cargo/xcframework/Platforms.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Platforms.swift
|
||||
// Cargo
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
import ArgumentParser
|
||||
import PackageModel
|
||||
|
||||
enum TargetPlatform: String, ExpressibleByArgument, CaseIterable {
|
||||
case ios
|
||||
case macos
|
||||
case maccatalyst
|
||||
case tvos
|
||||
case watchos
|
||||
|
||||
init?(argument: String) {
|
||||
self.init(rawValue: argument.lowercased())
|
||||
}
|
||||
|
||||
var platformName: String {
|
||||
switch self {
|
||||
case .ios: "ios"
|
||||
case .macos: "macos"
|
||||
case .maccatalyst: "macos"
|
||||
case .tvos: "tvos"
|
||||
case .watchos: "watchos"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Target SDKs
|
||||
|
||||
struct SDK {
|
||||
let destination: String
|
||||
let archiveName: String
|
||||
let releaseFolder: String
|
||||
let buildSettings: [String: String]?
|
||||
}
|
||||
|
||||
var sdks: [SDK] {
|
||||
switch self {
|
||||
case .ios:
|
||||
return [
|
||||
SDK(
|
||||
destination: "generic/platform=iOS",
|
||||
archiveName: "iphoneos.xcarchive",
|
||||
releaseFolder: "Release-iphoneos",
|
||||
buildSettings: nil
|
||||
),
|
||||
SDK(
|
||||
destination: "generic/platform=iOS Simulator",
|
||||
archiveName: "iphonesimulator.xcarchive",
|
||||
releaseFolder: "Release-iphonesimulator",
|
||||
buildSettings: nil
|
||||
)
|
||||
]
|
||||
|
||||
case .macos:
|
||||
return [
|
||||
SDK(
|
||||
destination: "generic/platform=macOS,name=Any Mac",
|
||||
archiveName: "macos.xcarchive",
|
||||
releaseFolder: "Release",
|
||||
buildSettings: nil
|
||||
)
|
||||
]
|
||||
|
||||
case .maccatalyst:
|
||||
return [
|
||||
SDK(
|
||||
destination: "generic/platform=macOS,variant=Mac Catalyst",
|
||||
archiveName: "maccatalyst.xcarchive",
|
||||
releaseFolder: "Release-maccatalyst",
|
||||
buildSettings: ["SUPPORTS_MACCATALYST": "YES"]
|
||||
)
|
||||
]
|
||||
|
||||
case .tvos:
|
||||
return [
|
||||
SDK(
|
||||
destination: "generic/platform=tvOS",
|
||||
archiveName: "appletvos.xcarchive",
|
||||
releaseFolder: "Release-appletvos",
|
||||
buildSettings: nil
|
||||
),
|
||||
SDK(
|
||||
destination: "generic/platform=tvOS Simulator",
|
||||
archiveName: "appletvsimulator.xcarchive",
|
||||
releaseFolder: "Release-appletvsimulator",
|
||||
buildSettings: nil
|
||||
)
|
||||
]
|
||||
|
||||
case .watchos:
|
||||
return [
|
||||
SDK(
|
||||
destination: "generic/platform=watchOS",
|
||||
archiveName: "watchos.xcarchive",
|
||||
releaseFolder: "Release-watchos",
|
||||
buildSettings: nil
|
||||
),
|
||||
SDK(
|
||||
destination: "generic/platform=watchOS Simulator",
|
||||
archiveName: "watchsimulator.xcarchive",
|
||||
releaseFolder: "Release-watchsimulator",
|
||||
buildSettings: nil
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
9
SideStoreApp/Sources/Cargo/xcframework/main.swift
Normal file
9
SideStoreApp/Sources/Cargo/xcframework/main.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// main.swift
|
||||
// Cargo
|
||||
//
|
||||
// Created by Joseph Mattiello on 02/28/23.
|
||||
// Copyright © 2023 Joseph Mattiello. All rights reserved.
|
||||
//
|
||||
|
||||
Command.main()
|
||||
Reference in New Issue
Block a user