@@ -10,32 +10,16 @@ See http://swift.org/CONTRIBUTORS.txt for Swift project authors
1010
1111import ArgumentParser
1212import Basics
13- import Build
14- import PackageGraph
1513import PackageModel
1614import TSCBasic
1715import ScriptParse
1816import ScriptingCore
19- import Foundation
20-
21- public extension SwiftToolOptions {
22- func redirect( to dir: AbsolutePath ) throws -> SwiftToolOptions {
23- var options = self
24- options. packagePath = dir
25- options. buildPath = nil
26- try options. validate ( )
27- return options
28- }
29- }
17+ import Workspace
3018
3119struct ScriptToolOptions : ParsableArguments {
3220 /// If the executable product should be built before running.
3321 @Flag ( name: . customLong( " skip-build " ) , help: " Skip building the executable product " )
3422 var shouldSkipBuild : Bool = false
35-
36- /// Whether to print build progress.
37- @Flag ( help: " Print build progress " )
38- var quiet : Bool = false
3923
4024 var shouldBuild : Bool { !shouldSkipBuild }
4125
@@ -59,6 +43,10 @@ public struct SwiftScriptTool: ParsableCommand {
5943 version: SwiftVersion . currentVersion. completeDisplayString,
6044 subcommands: [
6145 Run . self,
46+ Build . self,
47+ Clean . self,
48+ Reset . self,
49+ Resolve . self,
6250 ] ,
6351 helpNames: [ . short, . long, . customLong( " help " , withSingleDash: true ) ] )
6452
@@ -79,7 +67,7 @@ extension SwiftScriptTool {
7967
8068/// swift-run tool namespace
8169extension SwiftScriptTool {
82- struct Run : ParsableCommand {
70+ struct Run : ScriptCommand {
8371 static let configuration = CommandConfiguration (
8472 abstract: " Runs a script " )
8573
@@ -88,21 +76,22 @@ extension SwiftScriptTool {
8876
8977 @OptionGroup ( )
9078 var options : ScriptToolOptions
91-
92- func run( ) throws {
93- guard let file = options. file else {
94- throw ScriptError . fileNotFound ( " " )
95- }
96- let ( productName, cacheDirPath) = try prepareCache ( for: file, at: SwiftScriptTool . cacheDir)
9779
98- let swiftTool = try SwiftTool ( options: swiftOptions. redirect ( to: cacheDirPath) )
99- swiftTool. redirectStdoutToStderr ( )
80+ /// Whether to print build progress.
81+ @Flag ( help: " Print build progress " )
82+ var quiet : Bool = false
83+
84+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
10085 let output = BufferedOutputByteStream ( )
101- if options . quiet {
86+ if quiet {
10287 swiftTool. redirectStdoutTo ( . init( output) )
88+ } else {
89+ swiftTool. redirectStdoutToStderr ( )
10390 }
10491
10592 do {
93+ // FIXME: How to hide the note?
94+ swiftTool. diagnostics. emit ( note: " Using cache: \( cacheDirPath. basename) " )
10695 let buildSystem = try swiftTool. createBuildSystem ( explicitProduct: nil )
10796 if options. shouldBuild {
10897 try buildSystem. build ( subset: . product( productName) )
@@ -120,6 +109,164 @@ extension SwiftScriptTool {
120109 }
121110 }
122111 }
112+
113+ struct Build : ScriptCommand {
114+ static let configuration = CommandConfiguration (
115+ abstract: " Prebuild a script " )
116+
117+ @OptionGroup ( _hiddenFromHelp: true )
118+ var swiftOptions : SwiftToolOptions
119+
120+ @OptionGroup ( )
121+ var options : ScriptToolOptions
122+
123+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
124+ swiftTool. redirectStdoutToStderr ( )
125+
126+ do {
127+ swiftTool. diagnostics. emit ( note: " Using cache: \( cacheDirPath. basename) " )
128+ let buildSystem = try swiftTool. createBuildSystem ( explicitProduct: nil )
129+ if options. shouldBuild {
130+ try buildSystem. build ( subset: . product( productName) )
131+ }
132+ } catch let error as ScriptError {
133+ swiftTool. diagnostics. emit ( error)
134+ throw ExitCode . failure
135+ }
136+ }
137+ }
138+ }
139+
140+ extension SwiftScriptTool {
141+ struct Clean : ScriptCommand {
142+ static let configuration = CommandConfiguration (
143+ abstract: " Delete build artifacts " )
144+
145+ @OptionGroup ( _hiddenFromHelp: true )
146+ var swiftOptions : SwiftToolOptions
147+
148+ @OptionGroup ( )
149+ var options : ScriptToolOptions
150+
151+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
152+ try swiftTool. getActiveWorkspace ( ) . clean ( with: swiftTool. diagnostics)
153+ }
154+ }
155+
156+ struct Reset : ScriptCommand {
157+ static let configuration = CommandConfiguration (
158+ abstract: " Reset the complete cache directory " )
159+
160+ @OptionGroup ( _hiddenFromHelp: true )
161+ var swiftOptions : SwiftToolOptions
162+
163+ @OptionGroup ( )
164+ var options : ScriptToolOptions
165+
166+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
167+ try localFileSystem. removeFileTree ( cacheDirPath)
168+ }
169+ }
170+
171+ struct Update : ScriptCommand {
172+ static let configuration = CommandConfiguration (
173+ abstract: " Update package dependencies " )
174+
175+ @OptionGroup ( _hiddenFromHelp: true )
176+ var swiftOptions : SwiftToolOptions
177+
178+ @OptionGroup ( )
179+ var options : ScriptToolOptions
180+
181+ @Flag ( name: [ . long, . customShort( " n " ) ] ,
182+ help: " Display the list of dependencies that can be updated " )
183+ var dryRun : Bool = false
184+
185+ @Argument ( help: " The packages to update " )
186+ var packages : [ String ] = [ ]
187+
188+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
189+ let workspace = try swiftTool. getActiveWorkspace ( )
190+
191+ let changes = try workspace. updateDependencies (
192+ root: swiftTool. getWorkspaceRoot ( ) ,
193+ packages: packages,
194+ diagnostics: swiftTool. diagnostics,
195+ dryRun: dryRun
196+ )
197+
198+ // try to load the graph which will emit any errors
199+ if !swiftTool. diagnostics. hasErrors {
200+ _ = try workspace. loadPackageGraph (
201+ rootInput: swiftTool. getWorkspaceRoot ( ) ,
202+ diagnostics: swiftTool. diagnostics
203+ )
204+ }
205+
206+ if let pinsStore = swiftTool. diagnostics. wrap ( { try workspace. pinsStore. load ( ) } ) ,
207+ let changes = changes, dryRun {
208+ logPackageChanges ( changes: changes, pins: pinsStore)
209+ }
210+
211+ if !dryRun {
212+ // Throw if there were errors when loading the graph.
213+ // The actual errors will be printed before exiting.
214+ guard !swiftTool. diagnostics. hasErrors else {
215+ throw ExitCode . failure
216+ }
217+ }
218+ }
219+ }
220+ }
221+
222+ extension SwiftScriptTool {
223+ struct ResolveOptions : ParsableArguments {
224+ @Option ( help: " The version to resolve at " , transform: { Version ( string: $0) } )
225+ var version : Version ?
226+
227+ @Option ( help: " The branch to resolve at " )
228+ var branch : String ?
229+
230+ @Option ( help: " The revision to resolve at " )
231+ var revision : String ?
232+
233+ @Argument ( help: " The name of the package to resolve " )
234+ var packageName : String ?
235+ }
236+
237+ struct Resolve : ScriptCommand {
238+ static let configuration = CommandConfiguration (
239+ abstract: " Resolve package dependencies " )
240+
241+ @OptionGroup ( _hiddenFromHelp: true )
242+ var swiftOptions : SwiftToolOptions
243+
244+ @OptionGroup ( )
245+ var options : ScriptToolOptions
246+
247+ @OptionGroup ( )
248+ var resolveOptions : ResolveOptions
249+
250+ func run( _ swiftTool: SwiftTool , as productName: String , at cacheDirPath: AbsolutePath ) throws {
251+ // If a package is provided, use that to resolve the dependencies.
252+ if let packageName = resolveOptions. packageName {
253+ let workspace = try swiftTool. getActiveWorkspace ( )
254+ try workspace. resolve (
255+ packageName: packageName,
256+ root: swiftTool. getWorkspaceRoot ( ) ,
257+ version: resolveOptions. version,
258+ branch: resolveOptions. branch,
259+ revision: resolveOptions. revision,
260+ diagnostics: swiftTool. diagnostics)
261+ if swiftTool. diagnostics. hasErrors {
262+ throw ExitCode . failure
263+ }
264+ } else {
265+ // Otherwise, run a normal resolve.
266+ try swiftTool. resolve ( )
267+ }
268+ }
269+ }
123270}
124271
125272/// Executes the executable at the specified path.
@@ -136,3 +283,31 @@ fileprivate func run(
136283 let pathRelativeToWorkingDirectory = excutablePath. relative ( to: originalWorkingDirectory)
137284 try exec ( path: excutablePath. pathString, args: [ pathRelativeToWorkingDirectory. pathString] + arguments)
138285}
286+
287+ /// Logs all changed dependencies to a stream
288+ /// - Parameter changes: Changes to log
289+ /// - Parameter pins: PinsStore with currently pinned packages to compare changed packages to.
290+ /// - Parameter stream: Stream used for logging
291+ fileprivate func logPackageChanges( changes: [ ( PackageReference , Workspace . PackageStateChange ) ] , pins: PinsStore , on stream: OutputByteStream = TSCBasic . stdoutStream) {
292+ let changes = changes. filter { $0. 1 != . unchanged }
293+
294+ stream <<< " \n "
295+ stream <<< " \( changes. count) dependenc \( changes. count == 1 ? " y has " : " ies have " ) changed \( changes. count > 0 ? " : " : " . " ) "
296+ stream <<< " \n "
297+
298+ for (package , change) in changes {
299+ let currentVersion = pins. pinsMap [ package . identity] ? . state. description ?? " "
300+ switch change {
301+ case let . added( state) :
302+ stream <<< " + \( package . name) \( state. requirement. prettyPrinted) "
303+ case let . updated( state) :
304+ stream <<< " ~ \( package . name) \( currentVersion) -> \( package . name) \( state. requirement. prettyPrinted) "
305+ case . removed:
306+ stream <<< " - \( package . name) \( currentVersion) "
307+ case . unchanged:
308+ continue
309+ }
310+ stream <<< " \n "
311+ }
312+ stream. flush ( )
313+ }
0 commit comments