From 6e0e67f45dc33f9fc61300bb273281857c9dc40b Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 12:25:06 -0700 Subject: [PATCH 1/7] Add Linux support --- .github/workflows/ci.yml | 21 +++++++++++++++++---- Package.swift | 8 ++------ Sources/SafeDITool/SafeDITool.swift | 6 ++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d59e11e..592cd78b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: macos-13 steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Select Xcode Version run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer - name: Build and Test Framework @@ -28,7 +28,7 @@ jobs: runs-on: macos-13 steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Select Xcode Version run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer - name: Build Package Integration @@ -39,17 +39,30 @@ jobs: runs-on: macos-13 steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Select Xcode Version run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer - name: Build Project Integration run: pushd Examples/ExampleProjectIntegration; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -scheme ExampleProjectIntegration; popd + linux: + name: "Build and Test on Linux" + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + - name: Setup Swift Environment + uses: swift-actions/setup-swift@v1 + with: + swift-version: 5.9 + - name: Build and Test Framework + run: swift test -c release -Xswiftc -enable-testing + readme-validation: name: Check Markdown links runs-on: ubuntu-latest steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Markdown uses: gaurav-nelson/github-action-markdown-link-check@v1 diff --git a/Package.swift b/Package.swift index 51eee3d5..9ccc50d3 100644 --- a/Package.swift +++ b/Package.swift @@ -79,17 +79,13 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), + .byNameItem(name: "ZippyJSON", condition: .when(platforms: [.iOS, .tvOS, .macOS])), "SafeDICore", - "ZippyJSON", ] ), .testTarget( name: "SafeDIToolTests", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - "SafeDITool", - "ZippyJSON", - ] + dependencies: ["SafeDITool"] ), // Core diff --git a/Sources/SafeDITool/SafeDITool.swift b/Sources/SafeDITool/SafeDITool.swift index 5a33e846..f2077787 100644 --- a/Sources/SafeDITool/SafeDITool.swift +++ b/Sources/SafeDITool/SafeDITool.swift @@ -22,7 +22,9 @@ import ArgumentParser import Foundation import SafeDICore import SwiftParser +#if canImport(ZippyJSON) import ZippyJSON +#endif @main struct SafeDITool: AsyncParsableCommand { @@ -173,7 +175,11 @@ struct SafeDITool: AsyncParsableCommand { of: ModuleInfo.self, returning: [ModuleInfo].self ) { taskGroup in +#if canImport(ZippyJSON) let decoder = ZippyJSONDecoder() +#else + let decoder = JSONDecoder() +#endif for moduleInfoURL in moduleInfoURLs { taskGroup.addTask { try decoder.decode( From 9d4225d5aab06945a0c04c53d7b585ce703b901f Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 12:40:59 -0700 Subject: [PATCH 2/7] Do not use NSString's 'trimmingCharacters' API --- .../Generators/DependencyTreeGenerator.swift | 2 +- Sources/SafeDICore/Models/TypeDescription.swift | 14 +++++++------- Tests/SafeDICoreTests/TypeDescriptionTests.swift | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift index 7ebdbb06..5f6aabfd 100644 --- a/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift +++ b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift @@ -54,7 +54,7 @@ public final class DependencyTreeGenerator { for try await generatedRoot in taskGroup { generatedRoots.append(generatedRoot) } - return generatedRoots.sorted().joined(separator: "\n\n").trimmingCharacters(in: .whitespacesAndNewlines) + return generatedRoots.sorted().joined(separator: "\n\n") } let importsWhitespace = imports.isEmpty ? "" : "\n" diff --git a/Sources/SafeDICore/Models/TypeDescription.swift b/Sources/SafeDICore/Models/TypeDescription.swift index 95f908e1..e46e2218 100644 --- a/Sources/SafeDICore/Models/TypeDescription.swift +++ b/Sources/SafeDICore/Models/TypeDescription.swift @@ -121,7 +121,7 @@ public enum TypeDescription: Codable, Hashable, Comparable, Sendable { case let .closure(arguments, isAsync, doesThrow, returnType): return "(\(arguments.map { $0.asSource }.joined(separator: ", ")))\([isAsync ? " async" : "", doesThrow ? " throws" : ""].filter { !$0.isEmpty }.joined()) -> \(returnType.asSource)" case let .unknown(text): - return text.trimmingCharacters(in: .whitespacesAndNewlines) + return text } } @@ -478,7 +478,7 @@ extension TypeSyntax { } else { assertionFailure("TypeSyntax of unknown type. Defaulting to `description`.") // The description is a source-accurate description of this node, so it is a reasonable fallback. - return .unknown(text: description) + return .unknown(text: trimmedDescription) } } } @@ -500,7 +500,7 @@ extension ExprSyntax { if let base = memberAccessExpr.base { return base.typeDescription } else { - return .unknown(text: memberAccessExpr.description) + return .unknown(text: memberAccessExpr.trimmedDescription) } } else { if let base = memberAccessExpr.base { @@ -517,7 +517,7 @@ extension ExprSyntax { ) } } else { - return .unknown(text: memberAccessExpr.description) + return .unknown(text: memberAccessExpr.trimmedDescription) } } } else if let genericExpr = GenericSpecializationExprSyntax(self) { @@ -535,7 +535,7 @@ extension ExprSyntax { parentType: parentType, generics: genericTypeVisitor.genericArguments ) case .any, .array, .attributed, .closure, .composition, .dictionary, .implicitlyUnwrappedOptional, .metatype, .optional, .some, .tuple, .unknown: - return .unknown(text: description) + return .unknown(text: trimmedDescription) } } else if let tupleExpr = TupleExprSyntax(self) { let tupleTypes = tupleExpr.elements.map(\.expression.typeDescription) @@ -573,7 +573,7 @@ extension ExprSyntax { returnType: returnType.typeDescription ) } else { - return .unknown(text: description) + return .unknown(text: trimmedDescription) } } else if let optionalChainingExpr = OptionalChainingExprSyntax(self) { return .optional(optionalChainingExpr.expression.typeDescription) @@ -594,7 +594,7 @@ extension ExprSyntax { value: onlyElement.value.typeDescription ) } else { - return .unknown(text: description) + return .unknown(text: trimmedDescription) } } } diff --git a/Tests/SafeDICoreTests/TypeDescriptionTests.swift b/Tests/SafeDICoreTests/TypeDescriptionTests.swift index 09e068eb..c06bd740 100644 --- a/Tests/SafeDICoreTests/TypeDescriptionTests.swift +++ b/Tests/SafeDICoreTests/TypeDescriptionTests.swift @@ -882,8 +882,8 @@ final class TypeDescriptionTests: XCTestCase { } func test_asSource_whenDescribingAnUnknownCase_returnsTheProvidedStringWithWhitespaceStripped() { - let sut = TypeDescription.unknown(text: " SomeTypeThatIsFormattedOddly ") - XCTAssertEqual(sut.asSource, "SomeTypeThatIsFormattedOddly") + let typeDescription = TypeSyntax(stringLiteral: " SomeTypeThatIsFormattedOddly ").typeDescription + XCTAssertEqual(typeDescription.asSource, "SomeTypeThatIsFormattedOddly") } // MARK: - Visitors From 826b677e7da7cafecb3f7231ef848d83221c9536 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 12:53:07 -0700 Subject: [PATCH 3/7] Add transitive testTarget dependencies to satisfy linker --- Package.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 9ccc50d3..48b8ae7c 100644 --- a/Package.swift +++ b/Package.swift @@ -85,7 +85,11 @@ let package = Package( ), .testTarget( name: "SafeDIToolTests", - dependencies: ["SafeDITool"] + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .byNameItem(name: "ZippyJSON", condition: .when(platforms: [.iOS, .tvOS, .macOS])), + "SafeDITool", + ] ), // Core From b7fbeea81a00f4f0a24786c13552b3c23de9109c Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 13:23:55 -0700 Subject: [PATCH 4/7] Special-case Linux with #if check --- Sources/SafeDITool/SafeDITool.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/SafeDITool/SafeDITool.swift b/Sources/SafeDITool/SafeDITool.swift index f2077787..cb56ea6a 100644 --- a/Sources/SafeDITool/SafeDITool.swift +++ b/Sources/SafeDITool/SafeDITool.swift @@ -238,11 +238,15 @@ struct SafeDITool: AsyncParsableCommand { extension Data { fileprivate func write(toPath filePath: String) throws { +#if os(Linux) + try write(to: URL(fileURLWithPath: filePath)) +#else if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { try write(to: URL(filePath: filePath)) } else { try write(to: URL(fileURLWithPath: filePath)) } +#endif } } @@ -252,10 +256,14 @@ extension String { } fileprivate var asFileURL: URL { +#if os(Linux) + URL(fileURLWithPath: self) +#else if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { URL(filePath: self) } else { URL(fileURLWithPath: self) } +#endif } } From ae37d7379a4be1966855bdc12ac7c14f796a54d9 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 13:26:51 -0700 Subject: [PATCH 5/7] Upload code coverage from Linux --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 592cd78b..9c08c0a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,13 @@ jobs: with: swift-version: 5.9 - name: Build and Test Framework - run: swift test -c release -Xswiftc -enable-testing + run: swift test -c release --enable-code-coverage -Xswiftc -enable-testing + - name: Prepare Coverage Reports + run: | + llvm-cov export -format="lcov" .build/x86_64-unknown-linux-gnu/release/SafeDIPackageTests.xctest -instr-profile .build/x86_64-unknown-linux-gnu/release/codecov/default.profdata > coverage.lcov + - name: Upload Coverage Reports + if: success() + uses: codecov/codecov-action@v3 readme-validation: name: Check Markdown links From e8acaf417bcafa6885a8a2afdbd36c8b4c75d39f Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 14:00:18 -0700 Subject: [PATCH 6/7] Use string interpolation instead of 'description' on errors in tests --- Sources/SafeDICore/Generators/DependencyTreeGenerator.swift | 2 +- Tests/SafeDIToolTests/SafeDIToolTests.swift | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift index 5f6aabfd..4cd18b62 100644 --- a/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift +++ b/Sources/SafeDICore/Generators/DependencyTreeGenerator.swift @@ -69,7 +69,7 @@ public final class DependencyTreeGenerator { // MARK: - DependencyTreeGeneratorError - enum DependencyTreeGeneratorError: Error, CustomStringConvertible { + private enum DependencyTreeGeneratorError: Error, CustomStringConvertible { case noInstantiableFound(TypeDescription) case unfulfillableProperties([UnfulfillableProperty]) diff --git a/Tests/SafeDIToolTests/SafeDIToolTests.swift b/Tests/SafeDIToolTests/SafeDIToolTests.swift index d1bc826a..dc46b574 100644 --- a/Tests/SafeDIToolTests/SafeDIToolTests.swift +++ b/Tests/SafeDIToolTests/SafeDIToolTests.swift @@ -2732,13 +2732,11 @@ final class SafeDIToolTests: XCTestCase { line: UInt = #line, block: () async throws -> ReturnType ) async { - var didThrow = false do { _ = try await block() + XCTFail("Did not throw error!", line: line) } catch { - didThrow = true - XCTAssertEqual((error as CustomStringConvertible).description, errorDescription, line: line) + XCTAssertEqual("\(error)", errorDescription, line: line) } - XCTAssertTrue(didThrow, "Did not throw error!", line: line) } } From 972e65063e360c96dd0eccfbb857b34e101c2a09 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 10 Jan 2024 14:44:35 -0700 Subject: [PATCH 7/7] remove unnecessary assertion --- Sources/SafeDICore/Models/TypeDescription.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SafeDICore/Models/TypeDescription.swift b/Sources/SafeDICore/Models/TypeDescription.swift index e46e2218..d65a236c 100644 --- a/Sources/SafeDICore/Models/TypeDescription.swift +++ b/Sources/SafeDICore/Models/TypeDescription.swift @@ -476,7 +476,6 @@ extension TypeSyntax { returnType: typeIdentifier.returnClause.type.typeDescription) } else { - assertionFailure("TypeSyntax of unknown type. Defaulting to `description`.") // The description is a source-accurate description of this node, so it is a reasonable fallback. return .unknown(text: trimmedDescription) }