Files
JP Simard 76708d88f7 Remove Xcode project & Drop Carthage Support
Xcode has supported Swift Package Manager projects natively since Xcode
11, which has been out for over a year, where you can use Xcode by
having it open the `Package.swift` manifest rather than a `.xcodeproj`
or `.xcworkspace`.

I've been primarily developing SwiftLint in Xcode using this approach
for over a year, and despite a few nitpicks I have, it's been a joy to
use.

So I'd like to completely remove the Xcode project along with things
that support it:

* `SwiftLint.xcworkspace`/`SwiftLint.xcodeproj`: Neither of these are
  needed if we use `xed .` or `xed Package.swift` to develop SwiftLint
  in Xcode.
* `Cartfile`/`Cartfile.private`/`Cartfile.resolved`/`.gitmodules`/`Carthage/`:
  These were used to manage dependencies as git submodules for use in
  the Xcode workspace, but that's no longer necessary if Xcode delegates
  to SwiftPM to manage dependencies for us.
* Some scripts can be simplified, cleaned up or even completely removed.

* **Reduce duplication:** Rather than update dependencies or files in
  multiples places, there will only be the package manifest and the
  Podfile
* **Reduce merge conflicts:** This hasn't been too bad, but it's
  occasionally a pain when trying to land old PRs
* **Reduce development overhead:** No need to manually make sure that
  files in the Xcode project are sorted alphabetically
* **Reduce clone time:** No need to run
  `git submodules update --init --recursive` anymore

I think this breaks Carthage users that pull in SwiftLintFramework as a
dependency in a Cartfile.

However I can't find any references to [`SwiftLintFramework` in any
`Cartfile` files on GitHub](https://github.com/search?q=swiftlintframework+filename%3ACartfile&type=Code&ref=advsearch&l=&l=)
so this doesn't appear to be worth continuing to maintain.

Resolves https://github.com/realm/SwiftLint/issues/3412
2020-11-08 13:32:07 -05:00

251 lines
10 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
import SourceKittenFramework
@testable import SwiftLintFramework
import XCTest
private let config: Configuration = {
let directory = #file.bridge()
.deletingLastPathComponent.bridge()
.deletingLastPathComponent.bridge()
.deletingLastPathComponent
_ = FileManager.default.changeCurrentDirectoryPath(directory)
return Configuration(path: Configuration.fileName)
}()
class IntegrationTests: XCTestCase {
func testSwiftLintLints() {
// This is as close as we're ever going to get to a self-hosting linter.
let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false)
XCTAssert(swiftFiles.contains(where: { #file == $0.path }), "current file should be included")
let storage = RuleStorage()
let violations = swiftFiles.parallelFlatMap {
Linter(file: $0, configuration: config).collect(into: storage).styleViolations(using: storage)
}
violations.forEach { violation in
violation.location.file!.withStaticString {
XCTFail(violation.reason, file: $0, line: UInt(violation.location.line!))
}
}
}
func testSwiftLintAutoCorrects() {
let swiftFiles = config.lintableFiles(inPath: "", forceExclude: false)
let storage = RuleStorage()
let corrections = swiftFiles.parallelFlatMap {
Linter(file: $0, configuration: config).collect(into: storage).correct(using: storage)
}
for correction in corrections {
correction.location.file!.withStaticString {
XCTFail(correction.ruleDescription.description,
file: $0, line: UInt(correction.location.line!))
}
}
}
func testSimulateHomebrewTest() {
// Since this test uses the `swiftlint` binary built while building `SwiftLintPackageTests`,
// we run it only on macOS using SwiftPM.
#if os(macOS) && SWIFT_PACKAGE
let keyName = "SWIFTLINT_FRAMEWORK_TEST_ENABLE_SIMULATE_HOMEBREW_TEST"
guard ProcessInfo.processInfo.environment[keyName] != nil else {
print("""
Skipping the opt-in test `\(#function)`.
Set the `\(keyName)` environment variable to test `\(#function)`.
""")
return
}
guard let swiftlintURL = swiftlintBuiltBySwiftPM(),
let (testSwiftURL, seatbeltURL) = prepareSandbox() else {
return
}
defer {
try? FileManager.default.removeItem(at: testSwiftURL.deletingLastPathComponent())
try? FileManager.default.removeItem(at: seatbeltURL)
}
let swiftlintInSandboxArgs = ["sandbox-exec", "-f", seatbeltURL.path, "sh", "-c",
"SWIFTLINT_SWIFT_VERSION=3 \(swiftlintURL.path) --no-cache"]
let swiftlintResult = execute(swiftlintInSandboxArgs, in: testSwiftURL.deletingLastPathComponent())
let statusWithoutCrash: Int32 = 0
let stdoutWithoutCrash = """
\(testSwiftURL.path):1:1: \
warning: Trailing Newline Violation: Files should have a single trailing newline. (trailing_newline)
"""
let stderrWithoutCrash = """
Linting Swift files at paths \n\
Linting 'Test.swift' (1/1)
Connection invalid
Most rules will be skipped because sourcekitd has failed.
Done linting! Found 1 violation, 0 serious in 1 file.
"""
if #available(macOS 10.14.1, *) {
// Within a sandbox on macOS 10.14.1+, `swiftlint` crashes with "Test::Unit::AssertionFailedError"
// error in `libxpc.dylib` when calling `sourcekitd_send_request_sync`.
//
// Since Homebrew CI succeeded in bottling swiftlint 0.27.0 on release of macOS 10.14,
// `swiftlint` may not crash on macOS 10.14. But that is not confirmed.
XCTAssertNotEqual(swiftlintResult.status, statusWithoutCrash, "It is expected to crash.")
XCTAssertNotEqual(swiftlintResult.stdout, stdoutWithoutCrash)
XCTAssertNotEqual(swiftlintResult.stderr, stderrWithoutCrash)
} else {
XCTAssertEqual(swiftlintResult.status, statusWithoutCrash)
XCTAssertEqual(swiftlintResult.stdout, stdoutWithoutCrash)
XCTAssertEqual(swiftlintResult.stderr, stderrWithoutCrash)
}
#endif
}
}
private struct StaticStringImitator {
let string: String
func withStaticString(_ closure: (StaticString) -> Void) {
let isASCII = string.utf8.allSatisfy { $0 < 0x7f }
string.utf8CString.dropLast().withUnsafeBytes {
let imitator = Imitator(startPtr: $0.baseAddress!, utf8CodeUnitCount: $0.count, isASCII: isASCII)
closure(imitator.staticString)
}
}
struct Imitator {
let startPtr: UnsafeRawPointer
let utf8CodeUnitCount: Int
let flags: Int8
init(startPtr: UnsafeRawPointer, utf8CodeUnitCount: Int, isASCII: Bool) {
self.startPtr = startPtr
self.utf8CodeUnitCount = utf8CodeUnitCount
flags = isASCII ? 0x2 : 0x0
}
var staticString: StaticString {
return unsafeBitCast(self, to: StaticString.self)
}
}
}
private extension String {
func withStaticString(_ closure: (StaticString) -> Void) {
StaticStringImitator(string: self).withStaticString(closure)
}
}
#if os(macOS) && SWIFT_PACKAGE
private func execute(_ args: [String],
in directory: URL? = nil,
input: Data? = nil) -> (status: Int32, stdout: String, stderr: String) {
// swiftlint:disable:previous large_tuple
let process = Process()
process.launchPath = "/usr/bin/env"
process.arguments = args
if let directory = directory {
process.currentDirectoryPath = directory.path
}
let stdoutPipe = Pipe(), stderrPipe = Pipe()
process.standardOutput = stdoutPipe
process.standardError = stderrPipe
if let input = input {
let stdinPipe = Pipe()
process.standardInput = stdinPipe.fileHandleForReading
stdinPipe.fileHandleForWriting.write(input)
stdinPipe.fileHandleForWriting.closeFile()
}
let group = DispatchGroup(), queue = DispatchQueue.global()
var stdoutData: Data?, stderrData: Data?
process.launch()
queue.async(group: group) { stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile() }
queue.async(group: group) { stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile() }
process.waitUntilExit()
group.wait()
let stdout = stdoutData.flatMap { String(data: $0, encoding: .utf8) } ?? ""
let stderr = stderrData.flatMap { String(data: $0, encoding: .utf8) } ?? ""
return (process.terminationStatus, stdout, stderr)
}
private func prepareSandbox() -> (testSwiftURL: URL, seatbeltURL: URL)? {
// Since `/private/tmp` is hard coded in `/usr/local/Homebrew/Library/Homebrew/sandbox.rb`, we use them.
// /private/tmp
// ├── AADA6B05-2E06-4E7F-BA48-8B3AF44415E3
// │   └── Test.swift
// ├── AADA6B05-2E06-4E7F-BA48-8B3AF44415E3.sb
do {
// `/private/tmp` is standardized to `/tmp` that is symbolic link to `/private/tmp`.
let temporaryDirectoryURL = URL(fileURLWithPath: "/tmp").appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true)
let seatbeltURL = temporaryDirectoryURL.appendingPathExtension("sb")
try sandboxProfile().write(to: seatbeltURL, atomically: true, encoding: .utf8)
let testSwiftURL = temporaryDirectoryURL.appendingPathComponent("Test.swift")
try "import Foundation".write(to: testSwiftURL, atomically: true, encoding: .utf8)
return (testSwiftURL, seatbeltURL)
} catch {
XCTFail("\(error)")
return nil
}
}
private func sandboxProfile() -> String {
let homeDirectory = NSHomeDirectory()
return """
(version 1)
(debug deny) ; log all denied operations to /var/log/system.log
(allow file-write* (subpath "/private/tmp"))
(allow file-write* (subpath "/private/var/tmp"))
(allow file-write* (regex #"^/private/var/folders/[^/]+/[^/]+/[C,T]/"))
(allow file-write* (subpath "/private/tmp"))
(allow file-write* (subpath "\(homeDirectory)/Library/Caches/Homebrew"))
(allow file-write* (subpath "\(homeDirectory)/Library/Logs/Homebrew/swiftlint"))
(allow file-write* (subpath "\(homeDirectory)/Library/Developer"))
(allow file-write* (subpath "/usr/local/var/cache"))
(allow file-write* (subpath "/usr/local/var/homebrew/locks"))
(allow file-write* (subpath "/usr/local/var/log"))
(allow file-write* (subpath "/usr/local/var/run"))
(allow file-write*
(literal "/dev/ptmx")
(literal "/dev/dtracehelper")
(literal "/dev/null")
(literal "/dev/random")
(literal "/dev/zero")
(regex #"^/dev/fd/[0-9]+$")
(regex #"^/dev/ttys?[0-9]*$")
)
(deny file-write*) ; deny all other file write operations
(allow process-exec
(literal "/bin/ps")
(with no-sandbox)
) ; allow certain processes running without sandbox
(allow default) ; allow everything else
"""
}
private func swiftlintBuiltBySwiftPM() -> URL? {
#if DEBUG
let configuration = "debug"
#else
let configuration = "release"
#endif
let swiftBuildShowBinPathArgs = ["swift", "build", "--show-bin-path", "--configuration", configuration]
let binPathResult = execute(swiftBuildShowBinPathArgs)
guard binPathResult.status == 0 else {
let commandline = swiftBuildShowBinPathArgs.joined(separator: " ")
XCTFail("`\(commandline)` failed with status: \(binPathResult.status), error: \(binPathResult.stderr)")
return nil
}
let binPathString = binPathResult.stdout.components(separatedBy: CharacterSet.newlines).first!
let swiftlint = URL(fileURLWithPath: binPathString).appendingPathComponent("swiftlint")
guard FileManager.default.fileExists(atPath: swiftlint.path) else {
XCTFail("`swiftlint` does not exists.")
return nil
}
return swiftlint
}
#endif