diff --git a/Sources/ProjectSpec/Project.swift b/Sources/ProjectSpec/Project.swift index e872a27b..5c171f05 100644 --- a/Sources/ProjectSpec/Project.swift +++ b/Sources/ProjectSpec/Project.swift @@ -252,6 +252,21 @@ extension Project: PathContainer { extension Project { + /// Files excluded from cache tracking to match SourceGenerator's default exclusions. + /// Without this filter, changes to these files cause spurious project regeneration. + private static let defaultExcludedFileNames: Set = [".DS_Store"] + private static let defaultExcludedExtensions: Set = ["orig"] + + private static func isTrackedFile(_ path: Path) -> Bool { + if defaultExcludedFileNames.contains(path.lastComponent) { + return false + } + if let ext = path.extension, defaultExcludedExtensions.contains(ext) { + return false + } + return true + } + public var allTrackedFiles: [Path] { var files: [Path] = [] files.append(contentsOf: configFilePaths) @@ -270,7 +285,7 @@ extension Project { files.append(contentsOf: target.configFilePaths) for source in target.sources { let sourcePath = basePath + source.path - + let type = source.type ?? options.defaultSourceDirectoryType ?? .group if type.projectTracksChildren { let sourceChildren = (try? sourcePath.recursiveChildren()) ?? [] @@ -279,7 +294,7 @@ extension Project { files.append(sourcePath) } } - return files + return files.filter(Self.isTrackedFile) } } diff --git a/Tests/ProjectSpecTests/ProjectSpecTests.swift b/Tests/ProjectSpecTests/ProjectSpecTests.swift index c308df96..43914c30 100644 --- a/Tests/ProjectSpecTests/ProjectSpecTests.swift +++ b/Tests/ProjectSpecTests/ProjectSpecTests.swift @@ -591,6 +591,69 @@ class ProjectSpecTests: XCTestCase { } } + func testAllTrackedFilesExcludesIgnoredFiles() { + describe { + let directoryPath = Path(components: [NSTemporaryDirectory(), ProcessInfo.processInfo.globallyUniqueString]) + + $0.before { + try? directoryPath.delete() + } + + $0.after { + try? directoryPath.delete() + } + + $0.it("excludes .DS_Store and .orig files from tracked files") { + // Create source directory with a mix of valid and ignored files + let sourcesDir = directoryPath + "Sources" + try sourcesDir.mkpath() + try (sourcesDir + "main.swift").write("") + try (sourcesDir + ".DS_Store").write("") + try (sourcesDir + "file.swift.orig").write("") + + let target = Target( + name: "App", + type: .application, + platform: .iOS, + sources: [TargetSource(path: "Sources")] + ) + let project = Project(basePath: directoryPath, name: "Test", targets: [target]) + let trackedFiles = project.allTrackedFiles + + let trackedFileStrings = trackedFiles.map { $0.string } + let hasDSStore = trackedFileStrings.contains { $0.contains(".DS_Store") } + let hasOrig = trackedFileStrings.contains { $0.hasSuffix(".orig") } + let hasSwift = trackedFileStrings.contains { $0.contains("main.swift") } + + try expect(hasDSStore).to.beFalse() + try expect(hasOrig).to.beFalse() + try expect(hasSwift).to.beTrue() + } + + $0.it("excludes .DS_Store from fileGroup tracked files") { + // Create fileGroup directory with .DS_Store + let fileGroupDir = directoryPath + "Resources" + try fileGroupDir.mkpath() + try (fileGroupDir + "image.png").write("") + try (fileGroupDir + ".DS_Store").write("") + + let project = Project( + basePath: directoryPath, + name: "Test", + fileGroups: ["Resources"] + ) + let trackedFiles = project.allTrackedFiles + + let trackedFileStrings = trackedFiles.map { $0.string } + let hasDSStore = trackedFileStrings.contains { $0.contains(".DS_Store") } + let hasImage = trackedFileStrings.contains { $0.contains("image.png") } + + try expect(hasDSStore).to.beFalse() + try expect(hasImage).to.beTrue() + } + } + } + func testJSONEncodable() { describe { $0.it("encodes to json") {