Skip to content

Commit

Permalink
Simplify API, always consider absolute paths internally
Browse files Browse the repository at this point in the history
  • Loading branch information
jcrookebumble committed Mar 31, 2022
1 parent 8cd16c9 commit e743f56
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 46 deletions.
39 changes: 16 additions & 23 deletions Sources/XcodeProj/Objects/Files/PBXGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,25 @@ public extension PBXGroup {
.first(where: { $0.name == name })
}

/// Returns the file in the group with the given path, relative to a common base path.
/// - Note: Unlike the `file(named:)` API, considers the files's actual location on disk; therefore
/// can handle groups/projects that contain files with duplicated names.
/// `basePath` is likely to be the project's `$(SRCROOT)`, but can be any common root
/// between all files. Both arguments support traversal; i.e. `../`
/// Returns the file in the group with the given path, based on locations on-disk.
/// - Note: Paths function as-in Xcode; with respect to the group's `PBXSourceTree`.
/// Internally, identity is calculated based on normalized absolute paths for all files involved; i.e. actual
/// location in the file system.
///
/// - Parameters:
/// - path: a file path to search for.
/// - basePath: a base path to search from.
/// - Returns: file with the given absolute path contained in the given parent group.
func file(with path: Path, relativeTo basePath: Path) -> PBXFileReference? {
let normalized = path.normalized(relativeTo: basePath)
return children
.first { $0.normalizedPath(relativeTo: basePath) == normalized } as? PBXFileReference
/// - Returns: an existing reference to that file, or `nil` if not found
func file(with path: Path) -> PBXFileReference? {
func absolutePath(for fileRef: PBXFileElement) -> Path? {
try? fileRef.fullPath(sourceRoot: ".")
}

guard let groupAbsPath = absolutePath(for: self) else { return nil }
let fileNormalizedAbsolutePath = (groupAbsPath + path).normalize()
return children.first {
guard let candidateAbsPath = absolutePath(for: $0) else { return false }
return candidateAbsPath.normalize() == fileNormalizedAbsolutePath
} as? PBXFileReference
}

/// Creates a group with the given name and returns it.
Expand Down Expand Up @@ -251,15 +256,3 @@ public extension PBXGroup {
return fileReference
}
}

private extension PBXFileElement {
func normalizedPath(relativeTo sourceRoot: Path) -> Path? {
path.map { Path($0).normalized(relativeTo: sourceRoot) }
}
}

private extension Path {
func normalized(relativeTo path: Path) -> Path {
(path + self).normalize()
}
}
83 changes: 60 additions & 23 deletions Tests/XcodeProjTests/Objects/Files/PBXGroupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,37 +199,74 @@ final class PBXGroupTests: XCTestCase {
XCTAssertEqual(file!.sourceTree, .developerDir)
}

func test_whenSearchingForFiles_thenTheAbsolutePathIsUsedForUniqueness() throws {
func test_whenSearchingForFilesByPath_thenNormalizedAbsolutePathsAreUsedInternallyForUniqueness() throws {
let project = makeEmptyPBXProj()
let group = PBXGroup(children: [],
sourceTree: .absolute,
name: "group")
project.add(object: group)
let subDirectoryName = UUID().uuidString

func prepareDirectories() throws -> (projectDir: Path, groupDir: Path, group: PBXGroup) {
let absolutePath = try Path.uniqueTemporary()
let splitSize = 2
let projectDir = Path(components: absolutePath.components[0..<absolutePath.components.count-splitSize])
let groupDir = Path(components: absolutePath.components.reversed()[0..<splitSize].reversed())
XCTAssertEqual(projectDir + groupDir, absolutePath)

let subDir = absolutePath + subDirectoryName
try subDir.mkdir()

let group = PBXGroup(children: [], sourceTree: .group, name: "group", path: ".")
let parent = PBXGroup(children: [group], sourceTree: .absolute, name: "parent", path: projectDir.string)
project.add(object: parent)
project.add(object: group)

return (projectDir, groupDir, group)
}

let (projectDir, groupDir, theGroup) = try prepareDirectories()

let rootPath = try Path.uniqueTemporary()
let subdir = rootPath + "subdir"
try subdir.mkdir()
let fileName = UUID().uuidString
let file1Path = groupDir + fileName
let file2Path = groupDir + subDirectoryName + fileName

let file1 = rootPath + "file"
let file2 = rootPath + "subdir/file"
let fileReferenceFor = try [file1Path, file2Path]
.reduce([Path: PBXFileReference]()) { map, filePath in
let absolutePath = projectDir + filePath
try Data().write(to: absolutePath.url)
let file = try theGroup.addFile(
at: absolutePath,
sourceTree: .group,
sourceRoot: projectDir)

return map.merging([filePath: file]) { _, new in new }
}

let filesToRefs = try [file1, file2].reduce([Path: PBXFileReference]()) {
try Data().write(to: $1.url)
let file = try group.addFile(at: $1, sourceTree: .absolute, sourceRoot: rootPath)
return $0.merging([$1: file]) { _, new in new }
func assert(filePath: Path, hasReference: PBXFileReference?, line: UInt = #line) {
let actual = theGroup.file(with: filePath)
if let expected = hasReference {
XCTAssertEqual(actual, expected)
} else {
XCTAssertNil(actual)
}
}

XCTAssertEqual(
group.file(with: "file", relativeTo: rootPath),
try XCTUnwrap(filesToRefs[file1])
assert(
filePath: file1Path,
hasReference: fileReferenceFor[file1Path]
)
assert(
filePath: file2Path,
hasReference: fileReferenceFor[file2Path]
)
assert(
filePath: groupDir + subDirectoryName + ".." + fileName,
hasReference: fileReferenceFor[file1Path]
)
XCTAssertEqual(
group.file(with: "file", relativeTo: rootPath + "subdir"),
try XCTUnwrap(filesToRefs[file2])
assert(
filePath: groupDir + "foo/.." + subDirectoryName + fileName,
hasReference: fileReferenceFor[file2Path]
)
XCTAssertEqual(
group.file(with: "subdir/file", relativeTo: rootPath),
try XCTUnwrap(filesToRefs[file2])
assert(
filePath: Path(UUID().uuidString),
hasReference: nil
)
}

Expand Down

0 comments on commit e743f56

Please sign in to comment.