@@ -49,6 +49,9 @@ struct XcodeBSPApp: ParsableCommand {
4949 }
5050 }
5151
52+ let workspaceContainerPath = try resolveConfiguredWorkspaceContainerPath ( )
53+ print ( " Using workspace container: \( workspaceContainerPath) " )
54+
5255 let executablePath = try resolveConfiguredExecutablePath ( )
5356 print ( " Using executable path: \( executablePath) " )
5457
@@ -76,16 +79,25 @@ struct XcodeBSPApp: ParsableCommand {
7679 version: " 0.2.0 " ,
7780 bspVersion: " 2.0.0 " ,
7881 languages: [ " swift " , " objective-c " , " objective-cpp " ] ,
79- activeSchemes: activeSchemes
82+ activeSchemes: activeSchemes,
83+ buildBackend: . swiftBuild,
84+ workspaceContainerPath: workspaceContainerPath
8085 )
8186 let encoder = JSONEncoder ( )
8287 encoder. outputFormatting = . withoutEscapingSlashes
8388 let data = try encoder. encode ( config)
8489 FileManager . default. createFile ( atPath: configPath, contents: data)
8590 print ( " Saved BSP config to \( configPath) " )
8691 case . server:
87- let server = try XcodeBuildServer ( cacheDir: cacheDir)
88- server. run ( )
92+ let config = try FileConfigProvider ( ) . load ( decoder: JSONDecoder ( ) )
93+ switch config. buildBackend {
94+ case . swiftBuild:
95+ let server = try SwiftBuildServerBackend ( cacheDir: cacheDir, config: config)
96+ try server. run ( )
97+ case . xcodeBuild:
98+ let server = try XcodeBuildServer ( cacheDir: cacheDir)
99+ server. run ( )
100+ }
89101 }
90102 }
91103}
@@ -130,6 +142,63 @@ private extension XcodeBSPApp {
130142 return " /usr/local/bin/xcode-bsp "
131143 }
132144
145+ func resolveConfiguredWorkspaceContainerPath( fileManager: FileManager = . default) throws -> String {
146+ let candidates = try workspaceContainerCandidates ( fileManager: fileManager)
147+ guard candidates. isEmpty == false else {
148+ throw ValidationError (
149+ " No .xcworkspace or .xcodeproj found in the current directory. " +
150+ " Run `xcode-bsp config` from a project root. "
151+ )
152+ }
153+
154+ if candidates. count == 1 {
155+ return candidates [ 0 ]
156+ }
157+
158+ print ( " Choose workspace container for SwiftBuild: " )
159+ for (index, candidate) in candidates. enumerated ( ) {
160+ print ( " [ \( index + 1 ) ] \( candidate) " )
161+ }
162+
163+ print ( " Selection [1] " , terminator: " " )
164+ let input = ( readLine ( ) ?? " " ) . trimmingCharacters ( in: . whitespacesAndNewlines)
165+ if input. isEmpty {
166+ return candidates [ 0 ]
167+ }
168+
169+ guard let selectedIndex = Int ( input) , candidates. indices. contains ( selectedIndex - 1 ) else {
170+ throw ValidationError ( " Invalid container selection: \( input) " )
171+ }
172+
173+ return candidates [ selectedIndex - 1 ]
174+ }
175+
176+ func workspaceContainerCandidates( fileManager: FileManager ) throws -> [ String ] {
177+ let cwd = URL ( filePath: fileManager. currentDirectoryPath)
178+ let contents = try fileManager. contentsOfDirectory (
179+ at: cwd,
180+ includingPropertiesForKeys: [ . isDirectoryKey] ,
181+ options: [ . skipsHiddenFiles]
182+ )
183+
184+ let candidates = contents. compactMap { url -> String ? in
185+ let pathExtension = url. pathExtension. lowercased ( )
186+ guard pathExtension == " xcworkspace " || pathExtension == " xcodeproj " else {
187+ return nil
188+ }
189+ return url. lastPathComponent
190+ }
191+
192+ return candidates. sorted { lhs, rhs in
193+ let lhsIsWorkspace = lhs. lowercased ( ) . hasSuffix ( " .xcworkspace " )
194+ let rhsIsWorkspace = rhs. lowercased ( ) . hasSuffix ( " .xcworkspace " )
195+ if lhsIsWorkspace != rhsIsWorkspace {
196+ return lhsIsWorkspace
197+ }
198+ return lhs. localizedStandardCompare ( rhs) == . orderedAscending
199+ }
200+ }
201+
133202 func resolveExecutableCandidate( _ value: String , fileManager: FileManager ) -> String ? {
134203 guard value. contains ( " / " ) else {
135204 return nil
0 commit comments