diff --git a/Sources/PackageLoading/ToolsVersionParser.swift b/Sources/PackageLoading/ToolsVersionParser.swift index f38e2e4eb07..e3df3d9e0df 100644 --- a/Sources/PackageLoading/ToolsVersionParser.swift +++ b/Sources/PackageLoading/ToolsVersionParser.swift @@ -55,11 +55,7 @@ public struct ToolsVersionParser { throw ManifestParseError.emptyManifest(path: manifestPath) } - do { - return try self.parse(utf8String: manifestContentsDecodedWithUTF8) - } catch Error.malformedToolsVersionSpecification(.commentMarker(.isMissing)) { - throw UnsupportedToolsVersion(packageIdentity: .init(path: manifestPath), currentToolsVersion: .current, packageToolsVersion: .v3) - } + return try self.parse(utf8String: manifestContentsDecodedWithUTF8) } public static func parse(utf8String: String) throws -> ToolsVersion { @@ -645,8 +641,16 @@ extension ManifestLoader { do { regularManifestToolsVersion = try ToolsVersionParser.parse(manifestPath: regularManifest, fileSystem: fileSystem) } - catch let error as UnsupportedToolsVersion where error.packageToolsVersion == .v3 { - regularManifestToolsVersion = .v3 + catch let error as ToolsVersionParser.Error { + // If we have version-specific manifests, there are still more checks we must do if + // the error being thrown is that of a missing comment marker. + // Set the tools version to 3.1.0 since earlier packages default to this. + if case .malformedToolsVersionSpecification(.commentMarker(.isMissing)) = error, + !versionSpecificManifests.isEmpty { + regularManifestToolsVersion = .v3 + } else { + throw error + } } // Find the newest version-specific manifest that is compatible with the current tools version. diff --git a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift index c7c6b05778d..96c96d1c330 100644 --- a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift +++ b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift @@ -369,6 +369,26 @@ final class ToolsVersionParserTests: XCTestCase { } } + /// Verifies that the correct error is thrown for each Swift tools version specification missing entirely. + func testMissingSwiftToolsVersion() throws { + let manifestSnippetsWithoutVersionSpecifier = "\n import PackageDescription" + + // TODO bp update comments here, as this was copied from above test + XCTAssertThrowsError( + try self.parse(manifestSnippetsWithoutVersionSpecifier), + "a 'ToolsVersionParser.Error' should've been thrown, because the version specifier is missing from the Swift tools version specification" + ) { error in + guard let error = error as? ToolsVersionParser.Error, case .malformedToolsVersionSpecification(.commentMarker(.isMissing)) = error else { + XCTFail("'ToolsVersionLoader.Error.malformedToolsVersionSpecification(.versionSpecifier(.isMissing))' should've been thrown, but a different error is thrown") + return + } + XCTAssertEqual( + error.description, + "the manifest is missing a Swift tools version specification; consider prepending to the manifest '\(ToolsVersion.current.specification())' to specify the current Swift toolchain version as the lowest Swift version supported by the project; if such a specification already exists, consider moving it to the top of the manifest, or prepending it with '//' to help Swift Package Manager find it" + ) + } + } + /// Verifies that the correct error is thrown for each misspelt comment marker in Swift tools version specification. func testMisspeltSpecificationCommentMarkers() throws { let manifestSnippetsWithMisspeltSpecificationCommentMarker = [ @@ -837,4 +857,42 @@ final class ToolsVersionParserTests: XCTestCase { try version.validateToolsVersion(currentToolsVersion, packageIdentity: .plain("lunch")) XCTAssertEqual(version.description, "5.0.0") } + + func testMissingToolsVersionManifest() throws { + let fs = InMemoryFileSystem() + let root = AbsolutePath("/pkg") + + // First, test the missing comment marker with the latest tools version. + try fs.writeFileContents(root.appending("Package.swift"), string: "\n import PackageDescription") + XCTAssertThrowsError( + try ManifestLoader.findManifest(packagePath: root, fileSystem: fs, currentToolsVersion: .current) + ) { error in + guard let error = error as? ToolsVersionParser.Error, case .malformedToolsVersionSpecification(.commentMarker(.isMissing)) = error else { + XCTFail("'ToolsVersionParser.Error.malformedToolsVersionSpecification(.commentMarker(.isMissing))' should've been thrown, but a different error is thrown.") + return + } + + XCTAssertEqual( + error.description, + "the manifest is missing a Swift tools version specification; consider prepending to the manifest '\(ToolsVersion.current.specification())' to specify the current Swift toolchain version as the lowest Swift version supported by the project; if such a specification already exists, consider moving it to the top of the manifest, or prepending it with '//' to help Swift Package Manager find it" + ) + } + + // Next, test the missing comment marker with tools version .v4. This should default to using 3.1.0. + try fs.writeFileContents(root.appending("Package.swift"), string: "\n import PackageDescription") + XCTAssertThrowsError( + try ManifestLoader.findManifest(packagePath: root, fileSystem: fs, currentToolsVersion: .v4) + ) { error in + guard let error = error as? UnsupportedToolsVersion, error.packageToolsVersion == .v3 else { + XCTFail("'UnsupportedToolsVersion' should've been thrown, but a different error is thrown.") + return + } + + XCTAssertEqual( + error.description, + "the manifest is missing a Swift tools version specification; consider prepending to the manifest '\(ToolsVersion.current.specification())' to specify the current Swift toolchain version as the lowest Swift version supported by the project; if such a specification already exists, consider moving it to the top of the manifest, or prepending it with '//' to help Swift Package Manager find it" + ) + } + } + }