diff --git a/CHANGELOG.md b/CHANGELOG.md index d3db204f0..e25fc4304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed - Fix Carthage support https://github.com/xcodeswift/xcproj/pull/226 by @ileitch +- Fix adding file reference to bundle and package files https://github.com/xcodeswift/xcproj/pull/234 by @fuzza ### Changed - Carthage minimum Deployment Target: https://github.com/xcodeswift/xcproj/pull/229 by @olbrichj diff --git a/Fixtures/dummy.framework/.gitkeep b/Fixtures/dummy.framework/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/Sources/xcproj/PBXProjObjects+Helpers.swift b/Sources/xcproj/PBXProjObjects+Helpers.swift index a06fffcd6..e4c5fac0f 100644 --- a/Sources/xcproj/PBXProjObjects+Helpers.swift +++ b/Sources/xcproj/PBXProjObjects+Helpers.swift @@ -99,8 +99,8 @@ public extension PBXProj.Objects { sourceTree: PBXSourceTree = .group, sourceRoot: Path) throws -> ObjectReference { - guard filePath.isFile else { - throw XCodeProjEditingError.notAFile(path: filePath) + guard filePath.exists else { + throw XCodeProjEditingError.fileNotExists(path: filePath) } guard let groupReference = groups.first(where: { $0.value == toGroup })?.key else { @@ -200,13 +200,13 @@ public struct GroupAddingOptions: OptionSet { } public enum XCodeProjEditingError: Error, CustomStringConvertible { - case notAFile(path: Path) + case fileNotExists(path: Path) case groupNotFound(group: PBXGroup) public var description: String { switch self { - case .notAFile(let path): - return "\(path) is not a file path" + case .fileNotExists(let path): + return "\(path) does not exist" case .groupNotFound(let group): return "Group not found in project: \(group)" } diff --git a/Tests/xcprojTests/XCTestCase+Assertions.swift b/Tests/xcprojTests/XCTestCase+Assertions.swift index 47133ad13..9ad0cacce 100644 --- a/Tests/xcprojTests/XCTestCase+Assertions.swift +++ b/Tests/xcprojTests/XCTestCase+Assertions.swift @@ -2,6 +2,8 @@ import Foundation import XCTest extension XCTestCase { + typealias EquatableError = Error & Equatable + func XCTAssertNotNilAndUnwrap(_ obj: T?, message: String = "") -> T { guard let unwrappedObj = obj else { XCTAssertNotNil(obj, message) @@ -9,4 +11,16 @@ extension XCTestCase { } return unwrappedObj } + + func XCTAssertThrowsSpecificError(_ expression: @autoclosure () throws -> T, _ error: E, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { + XCTAssertThrowsError(expression, message, file: file, line: line) { actualError in + let message = "Expected \(error) got \(actualError)" + + guard let actualCastedError = actualError as? E else { + XCTFail(message, file: file, line: line) + return + } + XCTAssertEqual(actualCastedError, error, message, file: file, line: line) + } + } } diff --git a/Tests/xcprojTests/XcodeProjIntegrationSpec.swift b/Tests/xcprojTests/XcodeProjIntegrationSpec.swift index de2fa2db6..9d60b1a8b 100644 --- a/Tests/xcprojTests/XcodeProjIntegrationSpec.swift +++ b/Tests/xcprojTests/XcodeProjIntegrationSpec.swift @@ -138,34 +138,66 @@ final class XcodeProjIntegrationSpec: XCTestCase { XCTAssertEqual(project.pbxproj.objects.groups[newGroups[1].reference], newGroups[1].object) } - func test_add_new_file() throws { + func test_add_new_source_file() throws { let proj = projectiOS()!.pbxproj let filePath = fixturesPath() + "newfile.swift" let iOSGroup = proj.objects.group(named: "iOS", inGroup: proj.rootGroup)! let file = try proj.objects.addFile(at: filePath, toGroup: iOSGroup.object, sourceRoot: fixturesPath() + "iOS") + let expectedFile = PBXFileReference(sourceTree: .group, + name: "newfile.swift", + explicitFileType: "sourcecode.swift", + lastKnownFileType: "sourcecode.swift", + path: "../../newfile.swift") + XCTAssertEqual(proj.objects.fileReferences[file.reference], file.object) - XCTAssertEqual(file.object.name, "newfile.swift") - XCTAssertEqual(file.object.sourceTree, PBXSourceTree.group) - XCTAssertEqual(file.object.path, "../../newfile.swift") + XCTAssertEqual(file.object, expectedFile) XCTAssertNotNil(iOSGroup.object.children.index(of: file.reference)) let existingFile = try proj.objects.addFile(at: filePath, toGroup: proj.rootGroup, sourceRoot: fixturesPath() + "iOS") XCTAssertTrue(file == existingFile) } - - func test_add_not_a_file() throws { + + func test_add_new_dynamic_framework() throws { let proj = projectiOS()!.pbxproj - do { - _ = try proj.objects.addFile(at: fixturesPath() + "iOS/iOS", toGroup: proj.rootGroup, sourceRoot: fixturesPath() + "iOS") - XCTFail("Adding not file path should throw error") - } catch {} - - do { - _ = try proj.objects.addFile(at: fixturesPath() + "iOS/iOS/newfile.swift", toGroup: proj.rootGroup, sourceRoot: fixturesPath() + "iOS") - XCTFail("Adding not existing file should throw error") - } catch {} + let filePath = fixturesPath() + "dummy.framework" + + let iOSGroup = proj.objects.group(named: "iOS", inGroup: proj.rootGroup)! + let file = try proj.objects.addFile(at: filePath, + toGroup: iOSGroup.object, + sourceRoot: fixtureiOSSourcePath()) + + let expectedFile = PBXFileReference(sourceTree: .group, + name: "dummy.framework", + explicitFileType: "wrapper.framework", + lastKnownFileType: "wrapper.framework", + path: "../../dummy.framework") + + + XCTAssertEqual(proj.objects.fileReferences[file.reference], file.object) + XCTAssertEqual(file.object, expectedFile) + XCTAssertNotNil(iOSGroup.object.children.index(of: file.reference)) + } + + func test_add_existing_file_returns_existing_object() throws { + let proj = projectiOS()!.pbxproj + let filePath = fixturesPath() + "newfile.swift" + let iOSGroup = proj.objects.group(named: "iOS", inGroup: proj.rootGroup)!.object + + let file = try proj.objects.addFile(at: filePath, toGroup: iOSGroup, sourceRoot: fixtureiOSSourcePath()) + let existingFile = try proj.objects.addFile(at: filePath, toGroup: proj.rootGroup, sourceRoot: fixtureiOSSourcePath()) + XCTAssertTrue(file == existingFile) + } + + func test_add_nonexisting_file_throws() { + let proj = projectiOS()!.pbxproj + let filePath = fixturesPath() + "nonexisting.swift" + XCTAssertThrowsSpecificError( + try proj.objects.addFile(at: filePath, toGroup: proj.rootGroup, sourceRoot: fixtureiOSSourcePath()), + XCodeProjEditingError.fileNotExists(path: filePath), + "Adding a reference to non existing file should throw an error" + ) } func test_add_new_build_file() throws { @@ -255,6 +287,10 @@ final class XcodeProjIntegrationSpec: XCTestCase { return fixturesPath() + "iOS/Project.xcodeproj" } + private func fixtureiOSSourcePath() -> Path { + return fixturesPath() + "iOS" + } + private func projectiOS() -> XcodeProj? { return try? XcodeProj(path: fixtureiOSProjectPath()) } @@ -263,3 +299,18 @@ final class XcodeProjIntegrationSpec: XCTestCase { return try XcodeProj(path: fixtureWithoutWorkspaceProjectPath()) } } + +// This could be code generated (e.g. using sourcery) +extension XCodeProjEditingError: Equatable { + + static public func == (lhs: XCodeProjEditingError, rhs: XCodeProjEditingError) -> Bool { + switch (lhs, rhs) { + case (.fileNotExists(let path1), .fileNotExists(let path2)): + return path1 == path2 + case (.groupNotFound(let group1), .groupNotFound(let group2)): + return group1 == group2 + default: + return false + } + } +}