1212
1313import ArgumentParser
1414import Foundation
15+ import RegexBuilder
1516
1617fileprivate let modules : [ String ] = [
1718 " SwiftParser " ,
@@ -29,11 +30,12 @@ struct VerifySourceCode: ParsableCommand {
2930 var arguments : SourceCodeGeneratorArguments
3031
3132 func run( ) throws {
32- let executor = VerifySourceCodeExecutor (
33+ try VerifySpiYmlExecutor ( ) . run ( )
34+
35+ try VerifySourceCodeExecutor (
3336 toolchain: arguments. toolchain,
3437 verbose: arguments. verbose
35- )
36- try executor. run ( )
38+ ) . run ( )
3739 }
3840}
3941
@@ -101,3 +103,74 @@ struct VerifySourceCodeExecutor {
101103 }
102104 }
103105}
106+
107+ struct VerifySpiYmlExecutor {
108+ static let configuration = CommandConfiguration (
109+ abstract: " Verify that the .spi.yml file contains all libraries from Package.swift "
110+ )
111+
112+ /// Returns all libraries declared in `Package.swift`.
113+ ///
114+ /// Note: It would be nice if we could compile Package.swift with this file and reallly
115+ /// inspect the package targets instead of doing regex scraping, but this is good enough
116+ /// for now.
117+ private func librariesInPackageManifest( ) throws -> [ String ] {
118+ let extractNameRegex = Regex {
119+ #/^.*/#
120+ #".library(name: ""#
121+ Capture ( ZeroOrMore ( . word) )
122+ #"""#
123+ #/.*$/#
124+ }
125+ let packageFile = Paths . packageDir. appendingPathComponent ( " Package.swift " )
126+ let packageFileContents = try String ( contentsOf: packageFile)
127+ return
128+ packageFileContents
129+ . components ( separatedBy: " \n " )
130+ . filter ( { !$0. matches ( of: extractNameRegex) . isEmpty } )
131+ . map { $0. replacing ( extractNameRegex) { $0. 1 } }
132+ . sorted ( )
133+ }
134+ /// Returns all targets listed in `.spi.yml`.
135+ ///
136+ /// Note: It would be nice to actually parse the .yml file but then we would need to add
137+ /// a dependency from this script on a YAML parser and that just doesn’t seem worth it.
138+ private func targetsInSwiftPackageIndexManifest( ) throws -> [ String ] {
139+ let extractTargetRegex = Regex {
140+ #/^ - /#
141+ Capture ( ZeroOrMore ( . word) )
142+ #/$/#
143+ }
144+ let spiYmlFile = Paths . packageDir. appendingPathComponent ( " .spi.yml " )
145+ let spiYmlFileContents = try String ( contentsOf: spiYmlFile)
146+ return
147+ spiYmlFileContents
148+ . components ( separatedBy: " \n " )
149+ . filter ( { !$0. matches ( of: extractTargetRegex) . isEmpty } )
150+ . map { $0. replacing ( extractTargetRegex) { $0. 1 } }
151+ . sorted ( )
152+ }
153+
154+ func run( ) throws {
155+ logSection ( " Verifing that .spi.yml is up-to-date " )
156+
157+ let difference = try targetsInSwiftPackageIndexManifest ( ) . difference ( from: librariesInPackageManifest ( ) )
158+
159+ if !difference. isEmpty {
160+ let differenceDescription = difference. map { change in
161+ switch change {
162+ case . insert( _, let element, _) :
163+ return " - Unexpected in .spi.yml: \( element) "
164+ case . remove( _, let element, _) :
165+ return " - Missing in .spi.yml: \( element) "
166+ }
167+ } . joined ( separator: " \n " )
168+ throw ScriptExectutionError (
169+ message: """
170+ .spi.yml did not contain the same libraries as Package.swift:
171+ \( differenceDescription)
172+ """
173+ )
174+ }
175+ }
176+ }
0 commit comments