diff --git a/Mockingbird.xcodeproj/project.pbxproj b/Mockingbird.xcodeproj/project.pbxproj index becf404c..b9c7c999 100644 --- a/Mockingbird.xcodeproj/project.pbxproj +++ b/Mockingbird.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 2896E54626AA5A3E00124D02 /* spm-project-description.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2896E54326AA59ED00124D02 /* spm-project-description.json */; }; 2896E54726AA5A3F00124D02 /* generic-project-description.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2896E54426AA5A1900124D02 /* generic-project-description.json */; }; 28DAD96E251BDD66001A0B3F /* Project.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28DAD96D251BDD66001A0B3F /* Project.swift */; }; + 834F952C26ACDC1400FB0637 /* spm-dump-package.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 834F952B26ACDC0200FB0637 /* spm-dump-package.json */; }; 8356225C26A94CBE005CD5C5 /* TargetDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8356225B26A94CBE005CD5C5 /* TargetDescriptionTests.swift */; }; D314ED7F24CE1C10000CC23D /* GenericsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D314ED7E24CE1C10000CC23D /* GenericsTests.swift */; }; D3643B6C247B78A5002DF069 /* Function.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3643B6B247B78A4002DF069 /* Function.swift */; }; @@ -378,6 +379,7 @@ files = ( 2896E54626AA5A3E00124D02 /* spm-project-description.json in CopyFiles */, 2896E54726AA5A3F00124D02 /* generic-project-description.json in CopyFiles */, + 834F952C26ACDC1400FB0637 /* spm-dump-package.json in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -394,6 +396,7 @@ 2896E54326AA59ED00124D02 /* spm-project-description.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "spm-project-description.json"; sourceTree = ""; }; 2896E54426AA5A1900124D02 /* generic-project-description.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "generic-project-description.json"; sourceTree = ""; }; 28DAD96D251BDD66001A0B3F /* Project.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Project.swift; sourceTree = ""; }; + 834F952B26ACDC0200FB0637 /* spm-dump-package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "spm-dump-package.json"; sourceTree = ""; }; 8356225B26A94CBE005CD5C5 /* TargetDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetDescriptionTests.swift; sourceTree = ""; }; 942B00CDCB48ADC877A01AEE /* MockingbirdTestsHostMocks.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = MockingbirdTestsHostMocks.generated.swift; path = Tests/MockingbirdTests/Mocks/MockingbirdTestsHostMocks.generated.swift; sourceTree = ""; }; D314ED7E24CE1C10000CC23D /* GenericsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericsTests.swift; sourceTree = ""; }; @@ -768,6 +771,7 @@ isa = PBXGroup; children = ( 2896E54326AA59ED00124D02 /* spm-project-description.json */, + 834F952B26ACDC0200FB0637 /* spm-dump-package.json */, 2896E54426AA5A1900124D02 /* generic-project-description.json */, ); path = Resources; diff --git a/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift b/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift index e36e87dd..684a38b1 100644 --- a/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift +++ b/Sources/MockingbirdGenerator/Parser/Project/ProjectDescription.swift @@ -24,7 +24,7 @@ public struct TargetDescription: Hashable { return c99name ?? name.escapingForModuleName() } } - +/// Useful reference: https://github.com/apple/swift-package-manager/blob/main/Sources/PackageDescription/Target.swift extension TargetDescription: Codable { public enum CodingKeys: String, CodingKey, CaseIterable { case name @@ -45,13 +45,52 @@ extension TargetDescription: Codable { self.name = try container.decode(String.self, forKey: .name) self.c99name = try container.decodeIfPresent(String.self, forKey: .c99name) self.type = try container.decode(String.self, forKey: .type) - self.path = try container.decode(Path.self, forKey: .path) self.sources = try container.decodeIfPresent([Path].self, forKey: .sources) ?? [] + if let path = try container.decodeIfPresent(Path.self, forKey: .path) { + self.path = path + } else { + // if a custom path has not been set, swiftpm uses "Sources" and "Tests" relative to the package root. + // https://github.com/apple/swift-package-manager/blob/main/Sources/PackageDescription/Target.swift + let name = try container.decode(String.self, forKey: .name) + let type = try container.decode(String.self, forKey: .type) + if type == "regular" || type == "library" || type == "executable" { + self.path = Path("Sources/\(name)") + } else if type == "test" { + self.path = Path("Tests/\(name)") + } else { + self.path = Path(name) + } + } + let spmContainer = try decoder.container(keyedBy: SwiftPackageManagerKeys.self) - self.dependencies = try spmContainer.decodeIfPresent([String].self, forKey: .targetDependencies) - ?? container.decodeIfPresent([String].self, forKey: .dependencies) - ?? [] + let spmDependencies = try spmContainer.decodeIfPresent([String].self, forKey: .targetDependencies) + + if let dependencies = spmDependencies { + self.dependencies = dependencies + } else { + struct PackageDumpDependenciesMissingError: Error { } + do { + if let packageDumpDependencies = try container.decodeIfPresent([[String: [String?]]].self, forKey: .dependencies) { + self.dependencies = packageDumpDependencies.compactMap { element -> String? in + let values = element["product"] ?? element["byName"] ?? element["target"] + guard let first = values?.first else { return nil } + return first + } + } else { + throw PackageDumpDependenciesMissingError() + } + } catch DecodingError.typeMismatch, is PackageDumpDependenciesMissingError { + if let names = try container.decodeIfPresent([String].self, forKey: .dependencies) { + self.dependencies = names + } + else { + self.dependencies = [] + } + } catch { + throw error + } + } } } diff --git a/Tests/MockingbirdTests/Generator/Resources/spm-dump-package.json b/Tests/MockingbirdTests/Generator/Resources/spm-dump-package.json new file mode 100644 index 00000000..5b3c7888 --- /dev/null +++ b/Tests/MockingbirdTests/Generator/Resources/spm-dump-package.json @@ -0,0 +1,132 @@ +{ + "cLanguageStandard" : null, + "cxxLanguageStandard" : null, + "dependencies" : [ + { + "explicitName" : "swift-collections", + "name" : "swift-collections", + "productFilter" : null, + "requirement" : { + "range" : [ + { + "lowerBound" : "0.0.1", + "upperBound" : "1.0.0" + } + ] + }, + "url" : "https://github.com/apple/swift-collections" + }, + { + "explicitName" : "Mockingbird", + "name" : "Mockingbird", + "productFilter" : null, + "requirement" : { + "range" : [ + { + "lowerBound" : "0.16.0", + "upperBound" : "1.0.0" + } + ] + }, + "url" : "https://github.com/birdrides/mockingbird.git" + } + ], + "name" : "Feature", + "packageKind" : "root", + "pkgConfig" : null, + "platforms" : [ + + ], + "products" : [ + { + "name" : "Feature", + "targets" : [ + "FeatureTarget" + ], + "type" : { + "library" : [ + "automatic" + ] + } + } + ], + "providers" : null, + "swiftLanguageVersions" : null, + "targets" : [ + { + "dependencies" : [ + { + "target" : [ + "EmptyTarget", + null + ] + }, + { + "product" : [ + "Collections", + "swift-collections", + null + ] + } + ], + "exclude" : [ + + ], + "name" : "FeatureTarget", + "path" : "Sources/FeatureTargetSources", + "resources" : [ + + ], + "settings" : [ + + ], + "type" : "regular" + }, + { + "dependencies" : [ + + ], + "exclude" : [ + + ], + "name" : "EmptyTarget", + "resources" : [ + + ], + "settings" : [ + + ], + "type" : "regular" + }, + { + "dependencies" : [ + { + "byName" : [ + "FeatureTarget", + null + ] + }, + { + "byName" : [ + "Mockingbird", + null + ] + } + ], + "exclude" : [ + + ], + "name" : "FeatureTargetTests", + "resources" : [ + + ], + "settings" : [ + + ], + "type" : "test" + } + ], + "toolsVersion" : { + "_version" : "5.3.0" + } +} diff --git a/Tests/MockingbirdTests/Generator/TargetDescriptionTests.swift b/Tests/MockingbirdTests/Generator/TargetDescriptionTests.swift index f1e1c4cd..2f1aae01 100644 --- a/Tests/MockingbirdTests/Generator/TargetDescriptionTests.swift +++ b/Tests/MockingbirdTests/Generator/TargetDescriptionTests.swift @@ -14,12 +14,13 @@ class ProjectDescriptionDecodingTests: XCTestCase { XCTAssertEqual(decodedTarget.c99name, expectedTarget.c99name) XCTAssertEqual(decodedTarget.path, expectedTarget.path) XCTAssertEqual(decodedTarget.sources, expectedTarget.sources) - XCTAssertEqual(decodedTarget.dependencies, expectedTarget.dependencies) + XCTAssertEqual(decodedTarget.dependencies.sorted(), expectedTarget.dependencies.sorted()) } enum TestProjectDescription: String { case swiftPackageManager = "spm-project-description" case generic = "generic-project-description" + case swiftPackageManagerDumpPackage = "spm-dump-package" var name: String { return rawValue } struct LoadingError: LocalizedError { @@ -163,4 +164,56 @@ class ProjectDescriptionDecodingTests: XCTestCase { XCTFail("Did not decode \(expectedEmptyTarget.name) target.") } } + + func testParseSwiftPackageManagerDumpPackage() throws { + let json = try loadJSONProjectDescription(.swiftPackageManagerDumpPackage) + let description = try JSONDecoder().decode(ProjectDescription.self, from: json) + + XCTAssertEqual(description.targets.count, 3) + + // swift package package-dump does not include sources! + let expectedTestTarget = TargetDescription(name: "FeatureTargetTests", + c99name: nil, + type: "test", + path: "Tests/FeatureTargetTests", + sources: [], + dependencies: [ + "FeatureTarget", "Mockingbird" + ]) + + if let testTarget = description.targets.first(where: { $0.type == expectedTestTarget.type }) { + assertDecodedTarget(testTarget, isEqualTo: expectedTestTarget) + } else { + XCTFail("Did not decode test target.") + } + + let expectedLibraryTarget = TargetDescription(name: "FeatureTarget", + c99name: nil, + type: "regular", + path: "Sources/FeatureTargetSources", + sources: [], + dependencies: [ + "Collections", + "EmptyTarget", + ]) + + if let libraryTarget = description.targets.first(where: { $0.name == expectedLibraryTarget.name }) { + assertDecodedTarget(libraryTarget, isEqualTo: expectedLibraryTarget) + } else { + XCTFail("Did not decode \(expectedLibraryTarget.name) target.") + } + + let expectedEmptyTarget = TargetDescription(name: "EmptyTarget", + c99name: nil, + type: "regular", + path: "Sources/EmptyTarget", + sources: [], + dependencies: []) + + if let emptyTarget = description.targets.first(where: { $0.name == expectedEmptyTarget.name }) { + assertDecodedTarget(emptyTarget, isEqualTo: expectedEmptyTarget) + } else { + XCTFail("Did not decode \(expectedEmptyTarget.name) target.") + } + } }