@@ -86,47 +86,51 @@ private let depLineRegex = #/url:\s*"(?<url>https://[^"]+)".*exact:\s*"(?<versio
8686// Regex: checksum: "abc123..."
8787private let checksumRegex = #/checksum:\s*"(?<hash>[0-9a-f]{64})"/#
8888
89- /// Parse binary dependency URL + version from our Package.swift, then read checksum
90- /// from the upstream Package.swift in the local SPM checkout.
91- func parseBinaryDep( repoRoot: Path , spmDir: Path , pattern: String , xcfw: String ) throws -> BinaryDep {
92- let contents : String = try ( repoRoot + " Package.swift " ) . read ( )
93- guard let line = contents. components ( separatedBy: . newlines)
94- . first ( where: { $0. contains ( " github " ) && $0. contains ( pattern) } ) ,
95- let match = line. firstMatch ( of: depLineRegex)
96- else {
97- throw ValidationError ( " \( pattern) not found in Package.swift " )
98- }
99-
100- let repo = String ( match. url) . replacingOccurrences ( of: " .git " , with: " " )
101- let ver = String ( match. version)
102- let zipURL = " \( repo) /releases/download/ \( ver) / \( xcfw) .xcframework.zip "
89+ /// Reads and caches the main Package.swift contents.
90+ struct PackageManifest {
91+ let lines : [ String ]
10392
104- // Read checksum from local SPM checkout
105- let repoName = repo. components ( separatedBy: " / " ) . last ?? pattern
106- let checkoutPkg = spmDir + " checkouts " + repoName + " Package.swift "
107- guard checkoutPkg. exists else {
108- throw ValidationError ( " Upstream Package.swift not found at \( checkoutPkg) . Run SPM resolve first. " )
93+ init ( repoRoot: Path ) throws {
94+ let contents : String = try ( repoRoot + " Package.swift " ) . read ( )
95+ lines = contents. components ( separatedBy: . newlines)
10996 }
110- let upstreamContents : String = try checkoutPkg. read ( )
111- guard let csMatch = upstreamContents. firstMatch ( of: checksumRegex) else {
112- throw ValidationError ( " Could not find checksum in \( checkoutPkg) " )
97+
98+ /// Extract a full dependency line matching the given pattern.
99+ func dependencyLine( containing pattern: String ) throws -> String {
100+ guard let line = lines. first ( where: { $0. contains ( pattern) && $0. contains ( " .package " ) } )
101+ else { throw ValidationError ( " \( pattern) dependency not found in Package.swift " ) }
102+ return line
113103 }
114- return BinaryDep ( url: zipURL, checksum: String ( csMatch. hash) )
115- }
116104
117- func protobufDependencyLine( repoRoot: Path ) throws -> String {
118- let contents : String = try ( repoRoot + " Package.swift " ) . read ( )
119- guard let line = contents. components ( separatedBy: . newlines) . first ( where: { $0. contains ( " swift-protobuf " ) && $0. contains ( " .package " ) } )
120- else { throw ValidationError ( " swift-protobuf dependency not found in Package.swift " ) }
121- return line
105+ /// Parse binary dependency URL + version, then read checksum from the local SPM checkout.
106+ func binaryDep( spmDir: Path , pattern: String , xcfw: String ) throws -> BinaryDep {
107+ guard let line = lines. first ( where: { $0. contains ( " github " ) && $0. contains ( pattern) } ) ,
108+ let match = line. firstMatch ( of: depLineRegex)
109+ else { throw ValidationError ( " \( pattern) not found in Package.swift " ) }
110+
111+ let repo = String ( match. url) . replacingOccurrences ( of: " .git " , with: " " )
112+ let ver = String ( match. version)
113+ let zipURL = " \( repo) /releases/download/ \( ver) / \( xcfw) .xcframework.zip "
114+
115+ let repoName = repo. components ( separatedBy: " / " ) . last ?? pattern
116+ let checkoutPkg = spmDir + " checkouts " + repoName + " Package.swift "
117+ guard checkoutPkg. exists else {
118+ throw ValidationError ( " Upstream Package.swift not found at \( checkoutPkg) . Run SPM resolve first. " )
119+ }
120+ let upstreamContents : String = try checkoutPkg. read ( )
121+ guard let csMatch = upstreamContents. firstMatch ( of: checksumRegex) else {
122+ throw ValidationError ( " Could not find checksum in \( checkoutPkg) " )
123+ }
124+ return BinaryDep ( url: zipURL, checksum: String ( csMatch. hash) )
125+ }
122126}
123127
124128// MARK: - Xcode project generation
125129
126130/// Recursively add source files from a directory to an Xcode group and build phase.
127131func addSources(
128132 dir: Path , group: PBXGroup , sourcesBP: PBXSourcesBuildPhase , resourcesBP: PBXResourcesBuildPhase ,
129- pbxProj: PBXProj , repoRoot : Path , excludes: [ String ] = [ ]
133+ pbxProj: PBXProj , excludes: [ String ] = [ ]
130134) throws {
131135 for child in try dir. children ( ) . sorted ( by: { $0. string < $1. string } ) {
132136 let name = child. lastComponent
@@ -136,28 +140,23 @@ func addSources(
136140 let subgroup = PBXGroup ( sourceTree: . group, name: name, path: name)
137141 pbxProj. add ( object: subgroup)
138142 group. children. append ( subgroup)
139- try addSources ( dir: child, group: subgroup, sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj, repoRoot : repoRoot )
143+ try addSources ( dir: child, group: subgroup, sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj)
140144 } else {
141145 let ext = child. extension ?? " "
142146 let fileRef = PBXFileReference ( sourceTree: . absolute, name: name, path: child. string)
143147 pbxProj. add ( object: fileRef)
144148 group. children. append ( fileRef)
145149
150+ let bf = PBXBuildFile ( file: fileRef)
146151 switch ext {
147- case " swift " :
148- let bf = PBXBuildFile ( file: fileRef)
149- pbxProj. add ( object: bf)
150- sourcesBP. files? . append ( bf)
151- case " m " :
152- let bf = PBXBuildFile ( file: fileRef)
152+ case " swift " , " m " :
153153 pbxProj. add ( object: bf)
154154 sourcesBP. files? . append ( bf)
155155 case " xcprivacy " :
156- let bf = PBXBuildFile ( file: fileRef)
157156 pbxProj. add ( object: bf)
158157 resourcesBP. files? . append ( bf)
159158 default :
160- break // headers and other files are added to group but not to build phases
159+ break
161160 }
162161 }
163162 }
@@ -292,7 +291,7 @@ func generateFrameworkProject(at projectPath: Path, repoRoot: Path) throws {
292291 mainGroup. children. append ( liveKitGroup)
293292 try addSources (
294293 dir: repoRoot + " Sources " + " LiveKit " , group: liveKitGroup,
295- sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj, repoRoot : repoRoot ,
294+ sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj,
296295 excludes: [ " NOTICE " ]
297296 )
298297
@@ -302,7 +301,7 @@ func generateFrameworkProject(at projectPath: Path, repoRoot: Path) throws {
302301 mainGroup. children. append ( objcGroup)
303302 try addSources (
304303 dir: repoRoot + " Sources " + " LKObjCHelpers " , group: objcGroup,
305- sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj, repoRoot : repoRoot
304+ sourcesBP: sourcesBP, resourcesBP: resourcesBP, pbxProj: pbxProj
306305 )
307306
308307 // Make ObjC headers public in the headers build phase
@@ -387,8 +386,9 @@ struct BuildXCFramework: AsyncParsableCommand {
387386
388387 // --- Parse binary dependency info from local checkouts ---
389388 step ( " Reading binary dependency info... " )
390- let webrtcDep = try parseBinaryDep ( repoRoot: repoRoot, spmDir: spmDir, pattern: " webrtc-xcframework " , xcfw: " LiveKitWebRTC " )
391- let uniffiDep = try parseBinaryDep ( repoRoot: repoRoot, spmDir: spmDir, pattern: " uniffi-xcframework " , xcfw: " RustLiveKitUniFFI " )
389+ let manifest = try PackageManifest ( repoRoot: repoRoot)
390+ let webrtcDep = try manifest. binaryDep ( spmDir: spmDir, pattern: " webrtc-xcframework " , xcfw: " LiveKitWebRTC " )
391+ let uniffiDep = try manifest. binaryDep ( spmDir: spmDir, pattern: " uniffi-xcframework " , xcfw: " RustLiveKitUniFFI " )
392392 print ( " LiveKitWebRTC: \( webrtcDep. url) " )
393393 print ( " RustLiveKitUniFFI: \( uniffiDep. url) " )
394394
@@ -428,13 +428,10 @@ struct BuildXCFramework: AsyncParsableCommand {
428428 return results. sorted { $0. 0 . archiveName < $1. 0 . archiveName }
429429 }
430430
431- let archives = archiveResults. compactMap { p, r -> ( Platform , Path ) ? in
432- if case let . success( path) = r { return ( p, path) }
433- return nil
434- }
435- let failed = archiveResults. filter { if case . failure = $0. 1 { return true } ; return false }
436- if ! failed. isEmpty {
437- print ( " \( failed. count) platform(s) failed, \( archives. count) succeeded. " . yellow)
431+ let archives = archiveResults. compactMap { p, r -> ( Platform , Path ) ? in try ? ( p, r. get ( ) ) }
432+ let failedCount = archiveResults. count - archives. count
433+ if failedCount > 0 {
434+ print ( " \( failedCount) platform(s) failed, \( archives. count) succeeded. " . yellow)
438435 }
439436 guard !archives. isEmpty else {
440437 throw ValidationError ( " All platform archives failed. " )
@@ -463,7 +460,8 @@ struct BuildXCFramework: AsyncParsableCommand {
463460 step ( " Generating Package.swift... " )
464461 try generatePackageSwift (
465462 outputDir: outputDir, liveKitChecksum: liveKitChecksum,
466- webrtcDep: webrtcDep, uniffiDep: uniffiDep, repoRoot: repoRoot
463+ webrtcDep: webrtcDep, uniffiDep: uniffiDep,
464+ manifest: manifest, repoRoot: repoRoot
467465 )
468466 print ( " Written to \( outputDir) /Package.swift (local= \( local) ) " )
469467
@@ -484,7 +482,8 @@ struct BuildXCFramework: AsyncParsableCommand {
484482
485483 func generatePackageSwift(
486484 outputDir: Path , liveKitChecksum: String ,
487- webrtcDep: BinaryDep , uniffiDep: BinaryDep , repoRoot: Path
485+ webrtcDep: BinaryDep , uniffiDep: BinaryDep ,
486+ manifest: PackageManifest , repoRoot: Path
488487 ) throws {
489488 let tmpl : String = try ( repoRoot + " scripts " + " Package.swift.stencil " ) . read ( )
490489 let baseURL = version. map {
@@ -500,7 +499,7 @@ struct BuildXCFramework: AsyncParsableCommand {
500499 " webrtcChecksum " : webrtcDep. checksum,
501500 " uniffiURL " : uniffiDep. url,
502501 " uniffiChecksum " : uniffiDep. checksum,
503- " protobufDependency " : protobufDependencyLine ( repoRoot : repoRoot ) ,
502+ " protobufDependency " : manifest . dependencyLine ( containing : " swift-protobuf " ) ,
504503 ] )
505504 try ( outputDir + " Package.swift " ) . write ( rendered)
506505
0 commit comments