diff --git a/Sources/DependencyCalculator/DependencyGraph.swift b/Sources/DependencyCalculator/DependencyGraph.swift index c515078..cc36151 100644 --- a/Sources/DependencyCalculator/DependencyGraph.swift +++ b/Sources/DependencyCalculator/DependencyGraph.swift @@ -429,8 +429,7 @@ extension WorkspaceInfo { } /// Search all files specified in fileSystemSynchronizedGroups. - /// Currently, file extensions are note considered at all, so all files in the folder are subject to the search. - /// NOTE: FileSystemSynchronizedFileExceptionSet is not suppored yet. + /// Currently, file extensions are not considered at all, so all files in the folder are subject to the search. /// /// ref: https://github.com/tuist/XcodeGraph/pull/108 /// The implementation of `XcodeGraph` only considers cases where the root is a folder. @@ -450,8 +449,25 @@ extension WorkspaceInfo { folderPath = group.path.map { Path($0) } } guard let folderPath else { return } - paths.append(contentsOf: (try? folderPath.recursiveChildren()) ?? []) + + let membershipExceptionPaths = (group.exceptions ?? []) + .compactMap { $0 as? PBXFileSystemSynchronizedBuildFileExceptionSet } + .filter { $0.target.uuid == target.uuid } + .flatMap { $0.membershipExceptions ?? [] } + .map { folderPath + Path($0) } + let recursiveChildrenFilePaths = ((try? folderPath.recursiveChildren()) ?? []) + .filter { $0.isFile } + + paths.append(contentsOf: recursiveChildrenFilePaths.filter { filePath in + !membershipExceptionPaths.contains { filePath.isDescendantOrSelf(of: $0) } + }) } return paths } } + +private extension Path { + func isDescendantOrSelf(of path: Path) -> Bool { + self == path || string.hasPrefix(path.string + Path.separator) + } +} diff --git a/Tests/SelectiveTestingTests/ExampleProject/ExampleProject.xcodeproj/project.pbxproj b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject.xcodeproj/project.pbxproj index c3b31f5..6e7045f 100644 --- a/Tests/SelectiveTestingTests/ExampleProject/ExampleProject.xcodeproj/project.pbxproj +++ b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject.xcodeproj/project.pbxproj @@ -94,8 +94,19 @@ B24BBDBF2CAD7244005E6DAC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Example.strings; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 095D76502FD30A2E007174DE /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Path/ExceptionView.swift, + Path/ExcludedFolder, + ); + target = 276DB5BA29B144C900E5C615 /* ExampleProject */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ - 095EE0952DB8E35400EACE2E /* DeepFolder */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = DeepFolder; sourceTree = ""; }; + 095EE0952DB8E35400EACE2E /* DeepFolder */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (095D76502FD30A2E007174DE /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (Path/ExcludedFolder, ); path = DeepFolder; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ diff --git a/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExceptionView.swift b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExceptionView.swift new file mode 100644 index 0000000..4f226ea --- /dev/null +++ b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExceptionView.swift @@ -0,0 +1,7 @@ +import SwiftUI + +struct ExceptionView: View { + var body: some View { + EmptyView() + } +} diff --git a/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExcludedFolder/ExcludedFolderView.swift b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExcludedFolder/ExcludedFolderView.swift new file mode 100644 index 0000000..c283149 --- /dev/null +++ b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject/DeepFolder/Path/ExcludedFolder/ExcludedFolderView.swift @@ -0,0 +1,7 @@ +import SwiftUI + +struct ExcludedFolderView: View { + var body: some View { + EmptyView() + } +} diff --git a/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift index 1b5ea3a..d4d67d9 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift @@ -133,6 +133,42 @@ struct SelectiveTestingProjectTests { ])) } + @Test + func projectDeepFolderMembershipExceptionPathChange_turbo() async throws { + // given + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + + let tool = try testTool.createSUT(config: nil, + basePath: "ExampleProject.xcodeproj", + turbo: true) + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/ExceptionView.swift") + + // then + let result = try await tool.run() + #expect(result == Set()) + } + + @Test + func projectDeepFolderMembershipExceptionFolderChange_turbo() async throws { + // given + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + + let tool = try testTool.createSUT(config: nil, + basePath: "ExampleProject.xcodeproj", + turbo: true) + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/ExcludedFolder/ExcludedFolderView.swift") + + // then + let result = try await tool.run() + #expect(result == Set()) + } + @Test func projectTargetDependencyChange_turbo() async throws { // given @@ -176,6 +212,40 @@ struct SelectiveTestingProjectTests { ])) } + @Test + func projectDeepFolderMembershipExceptionPathChange() async throws { + // given + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + + let tool = try testTool.createSUT(config: nil, + basePath: "ExampleProject.xcodeproj") + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/ExceptionView.swift") + + // then + let result = try await tool.run() + #expect(result == Set()) + } + + @Test + func projectDeepFolderMembershipExceptionFolderChange() async throws { + // given + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + + let tool = try testTool.createSUT(config: nil, + basePath: "ExampleProject.xcodeproj") + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/ExcludedFolder/ExcludedFolderView.swift") + + // then + let result = try await tool.run() + #expect(result == Set()) + } + @Test func projectLocalizedPathChange() async throws { // given