@@ -253,6 +253,81 @@ struct XcodeGraphMapperTests {
253253 #expect( graph. dependencyConditions. isEmpty == true )
254254 }
255255
256+ @Test ( " Maps a workspace with multiple projects in different directories into a single graph " )
257+ func testWorkspaceGraphMultipleProjectsInDifferentDirectories( ) async throws {
258+ // Given
259+ //
260+ // A project structure like this:
261+ // .
262+ // ├── Workspace.xcworkspace
263+ // ├── App
264+ // │ └── ProjectA.xcodeproj
265+ // └── Modules
266+ // └── ProjectB.xcodeproj
267+ //
268+
269+ let pbxProjA = PBXProj ( )
270+ let pbxProjB = PBXProj ( )
271+
272+ let debug : XCBuildConfiguration = . testDebug( ) . add ( to: pbxProjA) . add ( to: pbxProjB)
273+ let releaseConfig : XCBuildConfiguration = . testRelease( ) . add ( to: pbxProjA) . add ( to: pbxProjB)
274+ let configurationList : XCConfigurationList = . test(
275+ buildConfigurations: [ debug, releaseConfig]
276+ )
277+ . add ( to: pbxProjA)
278+ . add ( to: pbxProjB)
279+
280+ let projectA = try await XcodeProj . test (
281+ projectName: " ProjectA " ,
282+ configurationList: configurationList,
283+ pbxProj: pbxProjA,
284+ path: " /tmp/App/ProjectA.xcodeproj "
285+ )
286+
287+ let projectB = try await XcodeProj . test (
288+ projectName: " ProjectB " ,
289+ configurationList: configurationList,
290+ pbxProj: pbxProjB,
291+ path: " /tmp/Modules/ProjectB/ProjectB.xcodeproj "
292+ )
293+
294+ let projectAPath = try #require( projectA. path? . string)
295+ let projectBPath = try #require( projectB. path? . string)
296+
297+ let xcworkspace = XCWorkspace (
298+ data: XCWorkspaceData (
299+ children: [
300+ . group( . init( location: . group( " App " ) , name: " App " , children: [
301+ . file( . init( location: . absolute( projectAPath) ) ) ,
302+ ] ) ) ,
303+ . group( . init( location: . group( " Modules " ) , name: " Modules " , children: [
304+ . file( . init( location: . absolute( projectBPath) ) ) ,
305+ ] ) ) ,
306+ ]
307+ ) ,
308+ path: . init( " /tmp/Workspace.xcworkspace " )
309+ )
310+
311+ try projectA. write ( path: projectA. path!)
312+ try projectB. write ( path: projectB. path!)
313+ let mapper = XcodeGraphMapper ( )
314+
315+ // When
316+ let graph = try await mapper. buildGraph ( from: . workspace( xcworkspace) )
317+
318+ // Then
319+
320+ // General workspace checks
321+ #expect( graph. workspace. name == " Workspace " )
322+ #expect( graph. workspace. projects. contains ( projectA. projectPath) == true )
323+ #expect( graph. workspace. projects. contains ( projectB. projectPath) == true )
324+ #expect( graph. projects. count == 2 )
325+
326+ // Nested paths are correct
327+ #expect( graph. projects [ " /tmp/App " ] != nil )
328+ #expect( graph. projects [ " /tmp/Modules/ProjectB " ] != nil )
329+ }
330+
256331 @Test ( " Maps a project graph with dependencies between targets " )
257332 func testGraphWithDependencies( ) async throws {
258333 // Given
0 commit comments